@@ -72,7 +83,7 @@ export default class ColumnSettings extends React.PureComponent {
-
+
{showPushSettings &&
}
@@ -83,7 +94,7 @@ export default class ColumnSettings extends React.PureComponent {
-
+
{showPushSettings &&
}
@@ -94,7 +105,7 @@ export default class ColumnSettings extends React.PureComponent {
-
+
{showPushSettings &&
}
@@ -105,12 +116,23 @@ export default class ColumnSettings extends React.PureComponent {
-
+
{showPushSettings && }
+
+
+
+
+
+
+ {showPushSettings && }
+
+
+
+
);
}
diff --git a/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.js b/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.js
new file mode 100644
index 0000000000..766c9bb5ba
--- /dev/null
+++ b/app/javascript/mastodon/features/notifications/components/notifications_permission_banner.js
@@ -0,0 +1,30 @@
+import React from 'react';
+import Icon from 'mastodon/components/icon';
+import Button from 'mastodon/components/button';
+import { requestBrowserPermission } from 'mastodon/actions/notifications';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import { FormattedMessage } from 'react-intl';
+
+export default @connect(() => {})
+class NotificationsPermissionBanner extends React.PureComponent {
+
+ static propTypes = {
+ dispatch: PropTypes.func.isRequired,
+ };
+
+ handleClick = () => {
+ this.props.dispatch(requestBrowserPermission());
+ }
+
+ render () {
+ return (
+
+ );
+ }
+
+}
diff --git a/app/javascript/mastodon/features/notifications/components/setting_toggle.js b/app/javascript/mastodon/features/notifications/components/setting_toggle.js
index e6f593ef89..c4c8bffbe3 100644
--- a/app/javascript/mastodon/features/notifications/components/setting_toggle.js
+++ b/app/javascript/mastodon/features/notifications/components/setting_toggle.js
@@ -12,6 +12,7 @@ export default class SettingToggle extends React.PureComponent {
label: PropTypes.node.isRequired,
onChange: PropTypes.func.isRequired,
defaultValue: PropTypes.bool,
+ disabled: PropTypes.bool,
}
onChange = ({ target }) => {
@@ -19,12 +20,12 @@ export default class SettingToggle extends React.PureComponent {
}
render () {
- const { prefix, settings, settingPath, label, defaultValue } = this.props;
+ const { prefix, settings, settingPath, label, defaultValue, disabled } = this.props;
const id = ['setting-toggle', prefix, ...settingPath].filter(Boolean).join('-');
return (
-
+
);
diff --git a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js
index a67f262953..9a70bd4f36 100644
--- a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js
+++ b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js
@@ -3,28 +3,55 @@ import { defineMessages, injectIntl } from 'react-intl';
import ColumnSettings from '../components/column_settings';
import { changeSetting } from '../../../actions/settings';
import { setFilter } from '../../../actions/notifications';
-import { clearNotifications } from '../../../actions/notifications';
+import { clearNotifications, requestBrowserPermission } from '../../../actions/notifications';
import { changeAlerts as changePushNotifications } from '../../../actions/push_notifications';
import { openModal } from '../../../actions/modal';
+import { showAlert } from '../../../actions/alerts';
const messages = defineMessages({
clearMessage: { id: 'notifications.clear_confirmation', defaultMessage: 'Are you sure you want to permanently clear all your notifications?' },
clearConfirm: { id: 'notifications.clear', defaultMessage: 'Clear notifications' },
+ permissionDenied: { id: 'notifications.permission_denied_alert', defaultMessage: 'Desktop notifications can\'t be enabled, as browser permission has been denied before' },
});
const mapStateToProps = state => ({
settings: state.getIn(['settings', 'notifications']),
pushSettings: state.get('push_notifications'),
+ alertsEnabled: state.getIn(['settings', 'notifications', 'alerts']).includes(true),
+ browserSupport: state.getIn(['notifications', 'browserSupport']),
+ browserPermission: state.getIn(['notifications', 'browserPermission']),
});
const mapDispatchToProps = (dispatch, { intl }) => ({
onChange (path, checked) {
if (path[0] === 'push') {
- dispatch(changePushNotifications(path.slice(1), checked));
+ if (checked && typeof window.Notification !== 'undefined' && Notification.permission !== 'granted') {
+ dispatch(requestBrowserPermission((permission) => {
+ if (permission === 'granted') {
+ dispatch(changePushNotifications(path.slice(1), checked));
+ } else {
+ dispatch(showAlert(undefined, messages.permissionDenied));
+ }
+ }));
+ } else {
+ dispatch(changePushNotifications(path.slice(1), checked));
+ }
} else if (path[0] === 'quickFilter') {
dispatch(changeSetting(['notifications', ...path], checked));
dispatch(setFilter('all'));
+ } else if (path[0] === 'alerts' && checked && typeof window.Notification !== 'undefined' && Notification.permission !== 'granted') {
+ if (checked && typeof window.Notification !== 'undefined' && Notification.permission !== 'granted') {
+ dispatch(requestBrowserPermission((permission) => {
+ if (permission === 'granted') {
+ dispatch(changeSetting(['notifications', ...path], checked));
+ } else {
+ dispatch(showAlert(undefined, messages.permissionDenied));
+ }
+ }));
+ } else {
+ dispatch(changeSetting(['notifications', ...path], checked));
+ }
} else {
dispatch(changeSetting(['notifications', ...path], checked));
}
@@ -38,6 +65,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}));
},
+ onRequestNotificationPermission () {
+ dispatch(requestBrowserPermission());
+ },
+
});
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ColumnSettings));
diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js
index 41a45b2b6c..73df7f49d0 100644
--- a/app/javascript/mastodon/features/notifications/index.js
+++ b/app/javascript/mastodon/features/notifications/index.js
@@ -12,6 +12,7 @@ import {
unmountNotifications,
markNotificationsAsRead,
} from '../../actions/notifications';
+import { submitMarkers } from '../../actions/markers';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import NotificationContainer from './containers/notification_container';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
@@ -24,6 +25,7 @@ import ScrollableList from '../../components/scrollable_list';
import LoadGap from '../../components/load_gap';
import Icon from 'mastodon/components/icon';
import compareId from 'mastodon/compare_id';
+import NotificationsPermissionBanner from './components/notifications_permission_banner';
const messages = defineMessages({
title: { id: 'column.notifications', defaultMessage: 'Notifications' },
@@ -54,6 +56,7 @@ const mapStateToProps = state => ({
numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size,
lastReadId: state.getIn(['notifications', 'readMarkerId']),
canMarkAsRead: state.getIn(['notifications', 'readMarkerId']) !== '0' && getNotifications(state).some(item => item !== null && compareId(item.get('id'), state.getIn(['notifications', 'readMarkerId'])) > 0),
+ needsNotificationPermission: state.getIn(['settings', 'notifications', 'alerts']).includes(true) && state.getIn(['notifications', 'browserSupport']) && state.getIn(['notifications', 'browserPermission']) === 'default',
});
export default @connect(mapStateToProps)
@@ -74,6 +77,7 @@ class Notifications extends React.PureComponent {
numPending: PropTypes.number,
lastReadId: PropTypes.string,
canMarkAsRead: PropTypes.bool,
+ needsNotificationPermission: PropTypes.bool,
};
static defaultProps = {
@@ -162,10 +166,11 @@ class Notifications extends React.PureComponent {
handleMarkAsRead = () => {
this.props.dispatch(markNotificationsAsRead());
+ this.props.dispatch(submitMarkers({ immediate: true }));
};
render () {
- const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar, lastReadId, canMarkAsRead } = this.props;
+ const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props;
const pinned = !!columnId;
const emptyMessage =
;
@@ -209,6 +214,8 @@ class Notifications extends React.PureComponent {
showLoading={isLoading && notifications.size === 0}
hasMore={hasMore}
numPending={numPending}
+ prepend={needsNotificationPermission &&
}
+ alwaysPrepend
emptyMessage={emptyMessage}
onLoadMore={this.handleLoadOlder}
onLoadPending={this.handleLoadPending}
diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js
index 9b03cf26d2..ecc0b8f0bd 100644
--- a/app/javascript/mastodon/features/ui/components/columns_area.js
+++ b/app/javascript/mastodon/features/ui/components/columns_area.js
@@ -8,6 +8,8 @@ import ReactSwipeableViews from 'react-swipeable-views';
import TabsBar, { links, getIndex, getLink } from './tabs_bar';
import { Link } from 'react-router-dom';
+import { disableSwiping } from 'mastodon/initial_state';
+
import BundleContainer from '../containers/bundle_container';
import ColumnLoading from './column_loading';
import DrawerLoading from './drawer_loading';
@@ -185,7 +187,7 @@ class ColumnsArea extends ImmutablePureComponent {
const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null :
;
const content = columnIndex !== -1 ? (
-
+
{links.map(this.renderView)}
) : (
diff --git a/app/javascript/mastodon/features/ui/components/focal_point_modal.js b/app/javascript/mastodon/features/ui/components/focal_point_modal.js
index 926a495d1c..e19f277d8e 100644
--- a/app/javascript/mastodon/features/ui/components/focal_point_modal.js
+++ b/app/javascript/mastodon/features/ui/components/focal_point_modal.js
@@ -20,6 +20,7 @@ import GIFV from 'mastodon/components/gifv';
import { me } from 'mastodon/initial_state';
import tesseractCorePath from 'tesseract.js-core/tesseract-core.wasm.js';
import tesseractWorkerPath from 'tesseract.js/dist/worker.min.js';
+import { assetHost } from 'mastodon/utils/config';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
@@ -50,8 +51,6 @@ const removeExtraLineBreaks = str => str.replace(/\n\n/g, '******')
.replace(/\n/g, ' ')
.replace(/\*\*\*\*\*\*/g, '\n\n');
-const assetHost = process.env.CDN_HOST || '';
-
class ImageLoader extends React.PureComponent {
static propTypes = {
diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js
index a02e3bbd74..54ec51fcfe 100644
--- a/app/javascript/mastodon/features/ui/components/media_modal.js
+++ b/app/javascript/mastodon/features/ui/components/media_modal.js
@@ -10,6 +10,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import ImageLoader from './image_loader';
import Icon from 'mastodon/components/icon';
import GIFV from 'mastodon/components/gifv';
+import { disableSwiping } from 'mastodon/initial_state';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
@@ -212,6 +213,7 @@ class MediaModal extends ImmutablePureComponent {
containerStyle={containerStyle}
onChangeIndex={this.handleSwipe}
index={index}
+ disabled={disableSwiping}
>
{content}
diff --git a/app/javascript/mastodon/features/ui/components/mute_modal.js b/app/javascript/mastodon/features/ui/components/mute_modal.js
index 852830c3c2..d8d8e68c38 100644
--- a/app/javascript/mastodon/features/ui/components/mute_modal.js
+++ b/app/javascript/mastodon/features/ui/components/mute_modal.js
@@ -1,25 +1,32 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
-import { injectIntl, FormattedMessage } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import Toggle from 'react-toggle';
import Button from '../../../components/button';
import { closeModal } from '../../../actions/modal';
import { muteAccount } from '../../../actions/accounts';
-import { toggleHideNotifications } from '../../../actions/mutes';
+import { toggleHideNotifications, changeMuteDuration } from '../../../actions/mutes';
+const messages = defineMessages({
+ minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' },
+ hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' },
+ days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
+ indefinite: { id: 'mute_modal.indefinite', defaultMessage: 'Indefinite' },
+});
const mapStateToProps = state => {
return {
account: state.getIn(['mutes', 'new', 'account']),
notifications: state.getIn(['mutes', 'new', 'notifications']),
+ muteDuration: state.getIn(['mutes', 'new', 'duration']),
};
};
const mapDispatchToProps = dispatch => {
return {
- onConfirm(account, notifications) {
- dispatch(muteAccount(account.get('id'), notifications));
+ onConfirm(account, notifications, muteDuration) {
+ dispatch(muteAccount(account.get('id'), notifications, muteDuration));
},
onClose() {
@@ -29,6 +36,10 @@ const mapDispatchToProps = dispatch => {
onToggleNotifications() {
dispatch(toggleHideNotifications());
},
+
+ onChangeMuteDuration(e) {
+ dispatch(changeMuteDuration(e.target.value));
+ },
};
};
@@ -43,6 +54,8 @@ class MuteModal extends React.PureComponent {
onConfirm: PropTypes.func.isRequired,
onToggleNotifications: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
+ muteDuration: PropTypes.number.isRequired,
+ onChangeMuteDuration: PropTypes.func.isRequired,
};
componentDidMount() {
@@ -51,7 +64,7 @@ class MuteModal extends React.PureComponent {
handleClick = () => {
this.props.onClose();
- this.props.onConfirm(this.props.account, this.props.notifications);
+ this.props.onConfirm(this.props.account, this.props.notifications, this.props.muteDuration);
}
handleCancel = () => {
@@ -66,8 +79,12 @@ class MuteModal extends React.PureComponent {
this.props.onToggleNotifications();
}
+ changeMuteDuration = (e) => {
+ this.props.onChangeMuteDuration(e);
+ }
+
render () {
- const { account, notifications } = this.props;
+ const { account, notifications, muteDuration, intl } = this.props;
return (
@@ -91,6 +108,21 @@ class MuteModal extends React.PureComponent {
+
+ :
+
+ {/* eslint-disable-next-line jsx-a11y/no-onchange */}
+
+
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index d05133507f..c6df49a5fb 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -266,7 +266,7 @@ class UI extends React.PureComponent {
handleWindowFocus = () => {
this.props.dispatch(focusApp());
- this.props.dispatch(submitMarkers());
+ this.props.dispatch(submitMarkers({ immediate: true }));
}
handleWindowBlur = () => {
@@ -366,10 +366,6 @@ class UI extends React.PureComponent {
navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage);
}
- if (typeof window.Notification !== 'undefined' && Notification.permission === 'default') {
- window.setTimeout(() => Notification.requestPermission(), 120 * 1000);
- }
-
this.props.dispatch(fetchMarkers());
this.props.dispatch(expandHomeTimeline());
this.props.dispatch(expandNotifications());
@@ -379,7 +375,7 @@ class UI extends React.PureComponent {
componentDidMount () {
this.hotkeys.__mousetrap__.stopCallback = (e, element) => {
- return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName) && !e.altKey;
+ return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName);
};
}
diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js
index 847d29dead..89b59051c6 100644
--- a/app/javascript/mastodon/initial_state.js
+++ b/app/javascript/mastodon/initial_state.js
@@ -26,5 +26,6 @@ export const usePendingItems = getMeta('use_pending_items');
export const showTrends = getMeta('trends');
export const title = getMeta('title');
export const cropImages = getMeta('crop_images');
+export const disableSwiping = getMeta('disable_swiping');
export default initialState;
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index fcec001e90..db1158e50c 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -167,10 +167,18 @@
},
{
"descriptors": [
+ {
+ "defaultMessage": "This page could not be displayed correctly. This error is likely caused by a browser add-on or automatic translation tools.",
+ "id": "error.unexpected_crash.explanation_addons"
+ },
{
"defaultMessage": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
"id": "error.unexpected_crash.explanation"
},
+ {
+ "defaultMessage": "Try disabling them and refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+ "id": "error.unexpected_crash.next_steps_addons"
+ },
{
"defaultMessage": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
"id": "error.unexpected_crash.next_steps"
@@ -265,6 +273,15 @@
],
"path": "app/javascript/mastodon/components/missing_indicator.json"
},
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Put it back",
+ "id": "picture_in_picture.restore"
+ }
+ ],
+ "path": "app/javascript/mastodon/components/picture_in_picture_placeholder.json"
+ },
{
"descriptors": [
{
@@ -633,6 +650,15 @@
],
"path": "app/javascript/mastodon/containers/status_container.json"
},
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Profile unavailable",
+ "id": "empty_column.account_unavailable"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/account_gallery/index.json"
+ },
{
"descriptors": [
{
@@ -796,6 +822,14 @@
"defaultMessage": "Show boosts from @{name}",
"id": "account.show_reblogs"
},
+ {
+ "defaultMessage": "Notify me when @{name} posts",
+ "id": "account.enable_notifications"
+ },
+ {
+ "defaultMessage": "Stop notifying me when @{name} posts",
+ "id": "account.disable_notifications"
+ },
{
"defaultMessage": "Pinned toots",
"id": "navigation_bar.pins"
@@ -2125,6 +2159,18 @@
"defaultMessage": "Delete",
"id": "confirmations.delete_list.confirm"
},
+ {
+ "defaultMessage": "Any followed user",
+ "id": "lists.replies_policy.all_replies"
+ },
+ {
+ "defaultMessage": "No one",
+ "id": "lists.replies_policy.no_replies"
+ },
+ {
+ "defaultMessage": "Members of the list",
+ "id": "lists.replies_policy.list_replies"
+ },
{
"defaultMessage": "Edit list",
"id": "lists.edit"
@@ -2133,6 +2179,10 @@
"defaultMessage": "Delete list",
"id": "lists.delete"
},
+ {
+ "defaultMessage": "Show replies to:",
+ "id": "lists.replies_policy.title"
+ },
{
"defaultMessage": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
"id": "empty_column.list"
@@ -2218,6 +2268,10 @@
"defaultMessage": "Push notifications",
"id": "notifications.column_settings.push"
},
+ {
+ "defaultMessage": "Desktop notifications are unavailable due to previously denied browser permissions request",
+ "id": "notifications.permission_denied"
+ },
{
"defaultMessage": "Quick filter bar",
"id": "notifications.column_settings.filter_bar.category"
@@ -2245,6 +2299,10 @@
{
"defaultMessage": "Poll results:",
"id": "notifications.column_settings.poll"
+ },
+ {
+ "defaultMessage": "New toots:",
+ "id": "notifications.column_settings.status"
}
],
"path": "app/javascript/mastodon/features/notifications/components/column_settings.json"
@@ -2271,6 +2329,10 @@
"defaultMessage": "Follows",
"id": "notifications.filter.follows"
},
+ {
+ "defaultMessage": "Updates from people you follow",
+ "id": "notifications.filter.statuses"
+ },
{
"defaultMessage": "All",
"id": "notifications.filter.all"
@@ -2313,6 +2375,10 @@
"defaultMessage": "{name} boosted your status",
"id": "notification.reblog"
},
+ {
+ "defaultMessage": "{name} just posted",
+ "id": "notification.status"
+ },
{
"defaultMessage": "{name} has requested to follow you",
"id": "notification.follow_request"
@@ -2320,6 +2386,23 @@
],
"path": "app/javascript/mastodon/features/notifications/components/notification.json"
},
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Never miss a thing",
+ "id": "notifications_permission_banner.title"
+ },
+ {
+ "defaultMessage": "To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled.",
+ "id": "notifications_permission_banner.how_to_control"
+ },
+ {
+ "defaultMessage": "Enable desktop notifications",
+ "id": "notifications_permission_banner.enable"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/notifications/components/notifications_permission_banner.json"
+ },
{
"descriptors": [
{
@@ -2329,6 +2412,10 @@
{
"defaultMessage": "Clear notifications",
"id": "notifications.clear"
+ },
+ {
+ "defaultMessage": "Desktop notifications can't be enabled, as browser permission has been denied before",
+ "id": "notifications.permission_denied_alert"
}
],
"path": "app/javascript/mastodon/features/notifications/containers/column_settings_container.json"
@@ -2339,6 +2426,10 @@
"defaultMessage": "Notifications",
"id": "column.notifications"
},
+ {
+ "defaultMessage": "Mark every notification as read",
+ "id": "notifications.mark_as_read"
+ },
{
"defaultMessage": "You don't have any notifications yet. Interact with others to start the conversation.",
"id": "empty_column.notifications"
@@ -2346,6 +2437,47 @@
],
"path": "app/javascript/mastodon/features/notifications/index.json"
},
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "Reply",
+ "id": "status.reply"
+ },
+ {
+ "defaultMessage": "Reply to thread",
+ "id": "status.replyAll"
+ },
+ {
+ "defaultMessage": "Boost",
+ "id": "status.reblog"
+ },
+ {
+ "defaultMessage": "Boost with original visibility",
+ "id": "status.reblog_private"
+ },
+ {
+ "defaultMessage": "Unboost",
+ "id": "status.cancel_reblog_private"
+ },
+ {
+ "defaultMessage": "This post cannot be boosted",
+ "id": "status.cannot_reblog"
+ },
+ {
+ "defaultMessage": "Favourite",
+ "id": "status.favourite"
+ },
+ {
+ "defaultMessage": "Reply",
+ "id": "confirmations.reply.confirm"
+ },
+ {
+ "defaultMessage": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
+ "id": "confirmations.reply.message"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/picture_in_picture/components/footer.json"
+ },
{
"descriptors": [
{
@@ -2798,6 +2930,14 @@
"defaultMessage": "Describe for the visually impaired",
"id": "upload_form.description"
},
+ {
+ "defaultMessage": "Analyzing picture…",
+ "id": "upload_modal.analyzing_picture"
+ },
+ {
+ "defaultMessage": "Preparing OCR…",
+ "id": "upload_modal.preparing_ocr"
+ },
{
"defaultMessage": "Edit media",
"id": "upload_modal.edit_media"
@@ -2810,10 +2950,6 @@
"defaultMessage": "Change thumbnail",
"id": "upload_form.thumbnail"
},
- {
- "defaultMessage": "Analyzing picture…",
- "id": "upload_modal.analyzing_picture"
- },
{
"defaultMessage": "Detect text from picture",
"id": "upload_modal.detect_text"
@@ -2910,6 +3046,22 @@
},
{
"descriptors": [
+ {
+ "defaultMessage": "{number, plural, one {# minute} other {# minutes}}",
+ "id": "intervals.full.minutes"
+ },
+ {
+ "defaultMessage": "{number, plural, one {# hour} other {# hours}}",
+ "id": "intervals.full.hours"
+ },
+ {
+ "defaultMessage": "{number, plural, one {# day} other {# days}}",
+ "id": "intervals.full.days"
+ },
+ {
+ "defaultMessage": "Indefinite",
+ "id": "mute_modal.indefinite"
+ },
{
"defaultMessage": "Are you sure you want to mute {name}?",
"id": "confirmations.mute.message"
@@ -2922,6 +3074,10 @@
"defaultMessage": "Hide notifications from this user?",
"id": "mute_modal.hide_notifications"
},
+ {
+ "defaultMessage": "Duration",
+ "id": "mute_modal.duration"
+ },
{
"defaultMessage": "Cancel",
"id": "confirmation_modal.cancel"
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index bb07bbc15b..e90be0a628 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -9,8 +9,10 @@
"account.browse_more_on_origin_server": "Browse more on the original profile",
"account.cancel_follow_request": "Cancel follow request",
"account.direct": "Direct message @{name}",
+ "account.disable_notifications": "Stop notifying me when @{name} posts",
"account.domain_blocked": "Domain blocked",
"account.edit_profile": "Edit profile",
+ "account.enable_notifications": "Notify me when @{name} posts",
"account.endorse": "Feature on profile",
"account.follow": "Follow",
"account.followers": "Followers",
@@ -170,7 +172,9 @@
"empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
"empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up",
"error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
+ "error.unexpected_crash.explanation_addons": "This page could not be displayed correctly. This error is likely caused by a browser add-on or automatic translation tools.",
"error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
+ "error.unexpected_crash.next_steps_addons": "Try disabling them and refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
"errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
"errors.unexpected_crash.report_issue": "Report issue",
"follow_request.authorize": "Authorize",
@@ -264,6 +268,10 @@
"lists.edit.submit": "Change title",
"lists.new.create": "Add list",
"lists.new.title_placeholder": "New list title",
+ "lists.replies_policy.all_replies": "Any followed user",
+ "lists.replies_policy.list_replies": "Members of the list",
+ "lists.replies_policy.no_replies": "No one",
+ "lists.replies_policy.title": "Show replies to:",
"lists.search": "Search among people you follow",
"lists.subheading": "Your lists",
"load_pending": "{count, plural, one {# new item} other {# new items}}",
@@ -271,7 +279,9 @@
"media_gallery.toggle_visible": "Hide {number, plural, one {image} other {images}}",
"missing_indicator.label": "Not found",
"missing_indicator.sublabel": "This resource could not be found",
+ "mute_modal.duration": "Duration",
"mute_modal.hide_notifications": "Hide notifications from this user?",
+ "mute_modal.indefinite": "Indefinite",
"navigation_bar.apps": "Mobile apps",
"navigation_bar.blocks": "Blocked users",
"navigation_bar.bookmarks": "Bookmarks",
@@ -303,6 +313,7 @@
"notification.own_poll": "Your poll has ended",
"notification.poll": "A poll you have voted in has ended",
"notification.reblog": "{name} boosted your toot",
+ "notification.status": "{name} just posted",
"notifications.clear": "Clear notifications",
"notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
"notifications.column_settings.alert": "Desktop notifications",
@@ -318,13 +329,22 @@
"notifications.column_settings.reblog": "Boosts:",
"notifications.column_settings.show": "Show in column",
"notifications.column_settings.sound": "Play sound",
+ "notifications.column_settings.status": "New toots:",
"notifications.filter.all": "All",
"notifications.filter.boosts": "Boosts",
"notifications.filter.favourites": "Favourites",
"notifications.filter.follows": "Follows",
"notifications.filter.mentions": "Mentions",
"notifications.filter.polls": "Poll results",
+ "notifications.filter.statuses": "Updates from people you follow",
"notifications.group": "{count} notifications",
+ "notifications.mark_as_read": "Mark every notification as read",
+ "notifications.permission_denied": "Desktop notifications are unavailable due to previously denied browser permissions request",
+ "notifications.permission_denied_alert": "Desktop notifications can't be enabled, as browser permission has been denied before",
+ "notifications_permission_banner.enable": "Enable desktop notifications",
+ "notifications_permission_banner.how_to_control": "To receive notifications when Mastodon isn't open, enable desktop notifications. You can control precisely which types of interactions generate desktop notifications through the {icon} button above once they're enabled.",
+ "notifications_permission_banner.title": "Never miss a thing",
+ "picture_in_picture.restore": "Put it back",
"poll.closed": "Closed",
"poll.refresh": "Refresh",
"poll.total_people": "{count, plural, one {# person} other {# people}}",
@@ -451,6 +471,7 @@
"upload_modal.detect_text": "Detect text from picture",
"upload_modal.edit_media": "Edit media",
"upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
+ "upload_modal.preparing_ocr": "Preparing OCR…",
"upload_modal.preview_label": "Preview ({ratio})",
"upload_progress.label": "Uploading...",
"video.close": "Close video",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index ef8d8d1e65..d26871c988 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -272,6 +272,8 @@
"missing_indicator.label": "見つかりません",
"missing_indicator.sublabel": "見つかりませんでした",
"mute_modal.hide_notifications": "このユーザーからの通知を隠しますか?",
+ "mute_modal.duration": "ミュートする期間",
+ "mute_modal.indefinite": "無期限",
"navigation_bar.apps": "アプリ",
"navigation_bar.blocks": "ブロックしたユーザー",
"navigation_bar.bookmarks": "ブックマーク",
diff --git a/app/javascript/mastodon/main.js b/app/javascript/mastodon/main.js
index da4884fd3d..bda51f692b 100644
--- a/app/javascript/mastodon/main.js
+++ b/app/javascript/mastodon/main.js
@@ -1,4 +1,5 @@
import * as registerPushNotifications from './actions/push_notifications';
+import { setupBrowserNotifications } from './actions/notifications';
import { default as Mastodon, store } from './containers/mastodon';
import React from 'react';
import ReactDOM from 'react-dom';
@@ -22,6 +23,7 @@ function main() {
const props = JSON.parse(mountNode.getAttribute('data-props'));
ReactDOM.render(
, mountNode);
+ store.dispatch(setupBrowserNotifications());
if (process.env.NODE_ENV === 'production') {
// avoid offline in dev mode because it's harder to debug
require('offline-plugin/runtime').install();
diff --git a/app/javascript/mastodon/reducers/mutes.js b/app/javascript/mastodon/reducers/mutes.js
index 4672e50974..a9eb61ff83 100644
--- a/app/javascript/mastodon/reducers/mutes.js
+++ b/app/javascript/mastodon/reducers/mutes.js
@@ -3,12 +3,14 @@ import Immutable from 'immutable';
import {
MUTES_INIT_MODAL,
MUTES_TOGGLE_HIDE_NOTIFICATIONS,
+ MUTES_CHANGE_DURATION,
} from '../actions/mutes';
const initialState = Immutable.Map({
new: Immutable.Map({
account: null,
notifications: true,
+ duration: 0,
}),
});
@@ -21,6 +23,8 @@ export default function mutes(state = initialState, action) {
});
case MUTES_TOGGLE_HIDE_NOTIFICATIONS:
return state.updateIn(['new', 'notifications'], (old) => !old);
+ case MUTES_CHANGE_DURATION:
+ return state.setIn(['new', 'duration'], Number(action.duration));
default:
return state;
}
diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js
index b01db806fa..1d48747176 100644
--- a/app/javascript/mastodon/reducers/notifications.js
+++ b/app/javascript/mastodon/reducers/notifications.js
@@ -10,6 +10,8 @@ import {
NOTIFICATIONS_MOUNT,
NOTIFICATIONS_UNMOUNT,
NOTIFICATIONS_MARK_AS_READ,
+ NOTIFICATIONS_SET_BROWSER_SUPPORT,
+ NOTIFICATIONS_SET_BROWSER_PERMISSION,
} from '../actions/notifications';
import {
ACCOUNT_BLOCK_SUCCESS,
@@ -40,6 +42,8 @@ const initialState = ImmutableMap({
readMarkerId: '0',
isTabVisible: true,
isLoading: false,
+ browserSupport: false,
+ browserPermission: 'default',
});
const notificationToMap = notification => ImmutableMap({
@@ -151,7 +155,7 @@ const deleteByStatus = (state, statusId) => {
const updateMounted = (state) => {
state = state.update('mounted', count => count + 1);
- if (!shouldCountUnreadNotifications(state)) {
+ if (!shouldCountUnreadNotifications(state, state.get('mounted') === 1)) {
state = state.set('readMarkerId', state.get('lastReadId'));
state = clearUnread(state);
}
@@ -167,14 +171,15 @@ const updateVisibility = (state, visibility) => {
return state;
};
-const shouldCountUnreadNotifications = (state) => {
+const shouldCountUnreadNotifications = (state, ignoreScroll = false) => {
const isTabVisible = state.get('isTabVisible');
const isOnTop = state.get('top');
const isMounted = state.get('mounted') > 0;
const lastReadId = state.get('lastReadId');
- const lastItemReached = !state.get('hasMore') || lastReadId === '0' || (!state.get('items').isEmpty() && compareId(state.get('items').last().get('id'), lastReadId) <= 0);
+ const lastItem = state.get('items').findLast(item => item !== null);
+ const lastItemReached = !state.get('hasMore') || lastReadId === '0' || (lastItem && compareId(lastItem.get('id'), lastReadId) <= 0);
- return !(isTabVisible && isOnTop && isMounted && lastItemReached);
+ return !(isTabVisible && (ignoreScroll || isOnTop) && isMounted && lastItemReached);
};
const recountUnread = (state, last_read_id) => {
@@ -241,6 +246,10 @@ export default function notifications(state = initialState, action) {
case NOTIFICATIONS_MARK_AS_READ:
const lastNotification = state.get('items').find(item => item !== null);
return lastNotification ? recountUnread(state, lastNotification.get('id')) : state;
+ case NOTIFICATIONS_SET_BROWSER_SUPPORT:
+ return state.set('browserSupport', action.value);
+ case NOTIFICATIONS_SET_BROWSER_PERMISSION:
+ return state.set('browserPermission', action.value);
default:
return state;
}
diff --git a/app/javascript/mastodon/reducers/relationships.js b/app/javascript/mastodon/reducers/relationships.js
index 1d050cc634..53949258a3 100644
--- a/app/javascript/mastodon/reducers/relationships.js
+++ b/app/javascript/mastodon/reducers/relationships.js
@@ -45,7 +45,7 @@ const initialState = ImmutableMap();
export default function relationships(state = initialState, action) {
switch(action.type) {
case ACCOUNT_FOLLOW_REQUEST:
- return state.setIn([action.id, action.locked ? 'requested' : 'following'], true);
+ return state.getIn([action.id, 'following']) ? state : state.setIn([action.id, action.locked ? 'requested' : 'following'], true);
case ACCOUNT_FOLLOW_FAIL:
return state.setIn([action.id, action.locked ? 'requested' : 'following'], false);
case ACCOUNT_UNFOLLOW_REQUEST:
diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js
index efef2ad9a5..057fa353a2 100644
--- a/app/javascript/mastodon/reducers/settings.js
+++ b/app/javascript/mastodon/reducers/settings.js
@@ -29,12 +29,13 @@ const initialState = ImmutableMap({
notifications: ImmutableMap({
alerts: ImmutableMap({
- follow: true,
+ follow: false,
follow_request: false,
- favourite: true,
- reblog: true,
- mention: true,
- poll: true,
+ favourite: false,
+ reblog: false,
+ mention: false,
+ poll: false,
+ status: false,
}),
quickFilter: ImmutableMap({
@@ -50,6 +51,7 @@ const initialState = ImmutableMap({
reblog: true,
mention: true,
poll: true,
+ status: true,
}),
sounds: ImmutableMap({
@@ -59,6 +61,7 @@ const initialState = ImmutableMap({
reblog: true,
mention: true,
poll: true,
+ status: true,
}),
}),
diff --git a/app/javascript/mastodon/utils/config.js b/app/javascript/mastodon/utils/config.js
new file mode 100644
index 0000000000..932cd0cbf5
--- /dev/null
+++ b/app/javascript/mastodon/utils/config.js
@@ -0,0 +1,10 @@
+import ready from '../ready';
+
+export let assetHost = '';
+
+ready(() => {
+ const cdnHost = document.querySelector('meta[name=cdn-host]');
+ if (cdnHost) {
+ assetHost = cdnHost.content || '';
+ }
+});
diff --git a/app/javascript/mastodon/utils/notifications.js b/app/javascript/mastodon/utils/notifications.js
new file mode 100644
index 0000000000..ab119c2e34
--- /dev/null
+++ b/app/javascript/mastodon/utils/notifications.js
@@ -0,0 +1,29 @@
+// Handles browser quirks, based on
+// https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API
+
+const checkNotificationPromise = () => {
+ try {
+ Notification.requestPermission().then();
+ } catch(e) {
+ return false;
+ }
+
+ return true;
+};
+
+const handlePermission = (permission, callback) => {
+ // Whatever the user answers, we make sure Chrome stores the information
+ if(!('permission' in Notification)) {
+ Notification.permission = permission;
+ }
+
+ callback(Notification.permission);
+};
+
+export const requestNotificationPermission = (callback) => {
+ if (checkNotificationPromise()) {
+ Notification.requestPermission().then((permission) => handlePermission(permission, callback));
+ } else {
+ Notification.requestPermission((permission) => handlePermission(permission, callback));
+ }
+};
diff --git a/app/javascript/packs/about.js b/app/javascript/packs/about.js
index 843cb2c87d..892d825ece 100644
--- a/app/javascript/packs/about.js
+++ b/app/javascript/packs/about.js
@@ -1,3 +1,4 @@
+import './public-path';
import loadPolyfills from '../mastodon/load_polyfills';
import { start } from '../mastodon/common';
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
index c65ebed74f..91240aecfb 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -1,3 +1,4 @@
+import './public-path';
import loadPolyfills from '../mastodon/load_polyfills';
import { start } from '../mastodon/common';
diff --git a/app/javascript/packs/common.js b/app/javascript/packs/common.js
index 5d42509c5f..05dff8e494 100644
--- a/app/javascript/packs/common.js
+++ b/app/javascript/packs/common.js
@@ -1 +1,2 @@
+import './public-path';
import 'styles/application.scss';
diff --git a/app/javascript/packs/error.js b/app/javascript/packs/error.js
index 685c890658..6376dc2f5d 100644
--- a/app/javascript/packs/error.js
+++ b/app/javascript/packs/error.js
@@ -1,3 +1,4 @@
+import './public-path';
import ready from '../mastodon/ready';
ready(() => {
diff --git a/app/javascript/packs/public-path.js b/app/javascript/packs/public-path.js
new file mode 100644
index 0000000000..f96109f4fc
--- /dev/null
+++ b/app/javascript/packs/public-path.js
@@ -0,0 +1,21 @@
+// Dynamically set webpack's loading path depending on a meta header, in order
+// to share the same assets regardless of instance configuration.
+// See https://webpack.js.org/guides/public-path/#on-the-fly
+
+function removeOuterSlashes(string) {
+ return string.replace(/^\/*/, '').replace(/\/*$/, '');
+}
+
+function formatPublicPath(host = '', path = '') {
+ let formattedHost = removeOuterSlashes(host);
+ if (formattedHost && !/^http/i.test(formattedHost)) {
+ formattedHost = `//${formattedHost}`;
+ }
+ const formattedPath = removeOuterSlashes(path);
+ return `${formattedHost}/${formattedPath}/`;
+}
+
+const cdnHost = document.querySelector('meta[name=cdn-host]');
+
+// eslint-disable-next-line camelcase, no-undef, no-unused-vars
+__webpack_public_path__ = formatPublicPath(cdnHost ? cdnHost.content : '', process.env.PUBLIC_OUTPUT_PATH);
diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js
index 5ad25a9b0f..3f67001959 100644
--- a/app/javascript/packs/public.js
+++ b/app/javascript/packs/public.js
@@ -1,3 +1,4 @@
+import './public-path';
import loadPolyfills from '../mastodon/load_polyfills';
import ready from '../mastodon/ready';
import { start } from '../mastodon/common';
diff --git a/app/javascript/packs/share.js b/app/javascript/packs/share.js
index 4ef23e1b2e..1225d7b529 100644
--- a/app/javascript/packs/share.js
+++ b/app/javascript/packs/share.js
@@ -1,3 +1,4 @@
+import './public-path';
import loadPolyfills from '../mastodon/load_polyfills';
import { start } from '../mastodon/common';
diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss
index 6b81e76237..64f4adb848 100644
--- a/app/javascript/styles/mastodon-light/diff.scss
+++ b/app/javascript/styles/mastodon-light/diff.scss
@@ -759,3 +759,8 @@ html {
.compose-form .compose-form__warning {
box-shadow: none;
}
+
+.mute-modal select {
+ border: 1px solid lighten($ui-base-color, 8%);
+ background: $simple-background-color url("data:image/svg+xml;utf8,
") no-repeat right 8px center / auto 16px;
+}
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index ff4bb34289..be554a5026 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -163,8 +163,7 @@
}
.icon-button {
- display: inline-flex;
- align-items: center;
+ display: inline-block;
padding: 0;
color: $action-button-color;
border: 0;
@@ -173,6 +172,7 @@
cursor: pointer;
transition: all 100ms ease-in;
transition-property: background-color, color;
+ text-decoration: none;
&:hover,
&:active,
@@ -247,6 +247,12 @@
}
}
+ &--with-counter {
+ display: inline-flex;
+ align-items: center;
+ width: auto !important;
+ }
+
&__counter {
display: inline-block;
width: 14px;
@@ -1152,6 +1158,10 @@
.status__action-bar-button {
margin-right: 18px;
+
+ &.icon-button--with-counter {
+ margin-right: 14px;
+ }
}
.status__action-bar-dropdown {
@@ -2408,6 +2418,17 @@ a.account__display-name {
line-height: 14px;
color: $primary-text-color;
}
+
+ &__issue-badge {
+ position: absolute;
+ left: 11px;
+ bottom: 1px;
+ display: block;
+ background: $error-red;
+ border-radius: 50%;
+ width: 0.625rem;
+ height: 0.625rem;
+ }
}
.column-link--transparent .icon-with-badge__badge {
@@ -3443,6 +3464,15 @@ a.status-card.compact:hover {
cursor: pointer;
}
+.column-header__issue-btn {
+ color: $warning-red;
+
+ &:hover {
+ color: $error-red;
+ text-decoration: underline;
+ }
+}
+
.column-header__icon {
display: inline-block;
margin-right: 5px;
@@ -3702,6 +3732,10 @@ a.status-card.compact:hover {
margin-bottom: 10px;
}
+.column-settings__row--with-margin {
+ margin-bottom: 15px;
+}
+
.column-settings__hashtags {
.column-settings__row {
margin-bottom: 15px;
@@ -5044,6 +5078,22 @@ a.status-card.compact:hover {
}
}
}
+
+ select {
+ appearance: none;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: $inverted-text-color;
+ display: inline-block;
+ width: auto;
+ outline: 0;
+ font-family: inherit;
+ background: $simple-background-color url("data:image/svg+xml;utf8,
") no-repeat right 8px center / auto 16px;
+ border: 1px solid darken($simple-background-color, 14%);
+ border-radius: 4px;
+ padding: 6px 10px;
+ padding-right: 30px;
+ }
}
.confirmation-modal__container,
@@ -7122,3 +7172,25 @@ noscript {
border-color: lighten($ui-base-color, 12%);
}
}
+
+.notifications-permission-banner {
+ padding: 30px;
+ border-bottom: 1px solid lighten($ui-base-color, 8%);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+
+ h2 {
+ font-size: 16px;
+ font-weight: 500;
+ margin-bottom: 15px;
+ text-align: center;
+ }
+
+ p {
+ color: $darker-text-color;
+ margin-bottom: 15px;
+ text-align: center;
+ }
+}
diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb
index 224451f417..2b5d3ffc29 100644
--- a/app/lib/activitypub/activity.rb
+++ b/app/lib/activitypub/activity.rb
@@ -74,7 +74,7 @@ class ActivityPub::Activity
@object_uri ||= begin
str = value_or_id(@object)
- if str.start_with?('bear:')
+ if str&.start_with?('bear:')
Addressable::URI.parse(str).query_values['u']
else
str
diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb
index 3f98dad2eb..3f2ae1106b 100644
--- a/app/lib/activitypub/tag_manager.rb
+++ b/app/lib/activitypub/tag_manager.rb
@@ -40,6 +40,10 @@ class ActivityPub::TagManager
end
end
+ def uri_for_username(username)
+ account_url(username: username)
+ end
+
def generate_uri_for(_target)
URI.join(root_url, 'payloads', SecureRandom.uuid)
end
diff --git a/app/lib/fast_ip_map.rb b/app/lib/fast_ip_map.rb
new file mode 100644
index 0000000000..ba30b45f33
--- /dev/null
+++ b/app/lib/fast_ip_map.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+class FastIpMap
+ MAX_IPV4_PREFIX = 32
+ MAX_IPV6_PREFIX = 128
+
+ # @param [Enumerable
] addresses
+ def initialize(addresses)
+ @fast_lookup = {}
+ @ranges = []
+
+ # Hash look-up is faster but only works for exact matches, so we split
+ # exact addresses from non-exact ones
+ addresses.each do |address|
+ if (address.ipv4? && address.prefix == MAX_IPV4_PREFIX) || (address.ipv6? && address.prefix == MAX_IPV6_PREFIX)
+ @fast_lookup[address.to_s] = true
+ else
+ @ranges << address
+ end
+ end
+
+ # We're more likely to hit wider-reaching ranges when checking for
+ # inclusion, so make sure they're sorted first
+ @ranges.sort_by!(&:prefix)
+ end
+
+ # @param [IPAddr] address
+ # @return [Boolean]
+ def include?(address)
+ @fast_lookup[address.to_s] || @ranges.any? { |cidr| cidr.include?(address) }
+ end
+end
diff --git a/app/lib/sanitize_config.rb b/app/lib/sanitize_config.rb
index ccc3f4642a..0fb415bd11 100644
--- a/app/lib/sanitize_config.rb
+++ b/app/lib/sanitize_config.rb
@@ -18,6 +18,7 @@ class Sanitize
gopher
xmpp
magnet
+ gemini
).freeze
CLASS_WHITELIST_TRANSFORMER = lambda do |env|
diff --git a/app/lib/settings/scoped_settings.rb b/app/lib/settings/scoped_settings.rb
index 8aa826561a..9889940f32 100644
--- a/app/lib/settings/scoped_settings.rb
+++ b/app/lib/settings/scoped_settings.rb
@@ -12,7 +12,6 @@ module Settings
@object = object
end
- # rubocop:disable Style/MethodMissingSuper
def method_missing(method, *args)
method_name = method.to_s
# set a value for a variable
@@ -25,7 +24,6 @@ module Settings
self[method_name]
end
end
- # rubocop:enable Style/MethodMissingSuper
def respond_to_missing?(*)
true
diff --git a/app/lib/user_settings_decorator.rb b/app/lib/user_settings_decorator.rb
index 2f9cfe3adf..581101782f 100644
--- a/app/lib/user_settings_decorator.rb
+++ b/app/lib/user_settings_decorator.rb
@@ -28,6 +28,7 @@ class UserSettingsDecorator
user.settings['display_media'] = display_media_preference if change?('setting_display_media')
user.settings['expand_spoilers'] = expand_spoilers_preference if change?('setting_expand_spoilers')
user.settings['reduce_motion'] = reduce_motion_preference if change?('setting_reduce_motion')
+ user.settings['disable_swiping'] = disable_swiping_preference if change?('setting_disable_swiping')
user.settings['system_font_ui'] = system_font_ui_preference if change?('setting_system_font_ui')
user.settings['system_emoji_font'] = system_emoji_font_preference if change?('setting_system_emoji_font')
user.settings['noindex'] = noindex_preference if change?('setting_noindex')
@@ -101,6 +102,10 @@ class UserSettingsDecorator
boolean_cast_setting 'setting_reduce_motion'
end
+ def disable_swiping_preference
+ boolean_cast_setting 'setting_disable_swiping'
+ end
+
def noindex_preference
boolean_cast_setting 'setting_noindex'
end
diff --git a/app/lib/webfinger.rb b/app/lib/webfinger.rb
new file mode 100644
index 0000000000..b2374c4941
--- /dev/null
+++ b/app/lib/webfinger.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+class Webfinger
+ class Error < StandardError; end
+
+ class Response
+ def initialize(body)
+ @json = Oj.load(body, mode: :strict)
+ end
+
+ def subject
+ @json['subject']
+ end
+
+ def link(rel, attribute)
+ links.dig(rel, attribute)
+ end
+
+ private
+
+ def links
+ @links ||= @json['links'].map { |link| [link['rel'], link] }.to_h
+ end
+ end
+
+ def initialize(uri)
+ _, @domain = uri.split('@')
+
+ raise ArgumentError, 'Webfinger requested for local account' if @domain.nil?
+
+ @uri = uri
+ end
+
+ def perform
+ Response.new(body_from_webfinger)
+ rescue Oj::ParseError
+ raise Webfinger::Error, "Invalid JSON in response for #{@uri}"
+ rescue Addressable::URI::InvalidURIError
+ raise Webfinger::Error, "Invalid URI for #{@uri}"
+ end
+
+ private
+
+ def body_from_webfinger(url = standard_url, use_fallback = true)
+ webfinger_request(url).perform do |res|
+ if res.code == 200
+ res.body_with_limit
+ elsif res.code == 404 && use_fallback
+ body_from_host_meta
+ else
+ raise Webfinger::Error, "Request for #{@uri} returned HTTP #{res.code}"
+ end
+ end
+ end
+
+ def body_from_host_meta
+ host_meta_request.perform do |res|
+ if res.code == 200
+ body_from_webfinger(url_from_template(res.body_with_limit), false)
+ else
+ raise Webfinger::Error, "Request for #{@uri} returned HTTP #{res.code}"
+ end
+ end
+ end
+
+ def url_from_template(str)
+ link = Nokogiri::XML(str).at_xpath('//xmlns:Link[@rel="lrdd"]')
+
+ if link.present?
+ link['template'].gsub('{uri}', @uri)
+ else
+ raise Webfinger::Error, "Request for #{@uri} returned host-meta without link to Webfinger"
+ end
+ rescue Nokogiri::XML::XPath::SyntaxError
+ raise Webfinger::Error, "Invalid XML encountered in host-meta for #{@uri}"
+ end
+
+ def host_meta_request
+ Request.new(:get, host_meta_url).add_headers('Accept' => 'application/xrd+xml, application/xml, text/xml')
+ end
+
+ def webfinger_request(url)
+ Request.new(:get, url).add_headers('Accept' => 'application/jrd+json, application/json')
+ end
+
+ def standard_url
+ "https://#{@domain}/.well-known/webfinger?resource=#{@uri}"
+ end
+
+ def host_meta_url
+ "https://#{@domain}/.well-known/host-meta"
+ end
+end
diff --git a/app/models/account.rb b/app/models/account.rb
index 3a6b38181b..38f235baab 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -354,6 +354,12 @@ class Account < ApplicationRecord
shared_inbox_url.presence || inbox_url
end
+ def synchronization_uri_prefix
+ return 'local' if local?
+
+ @synchronization_uri_prefix ||= uri[/http(s?):\/\/[^\/]+\//]
+ end
+
class Field < ActiveModelSerializers::Model
attributes :name, :value, :verified_at, :account, :errors
diff --git a/app/models/account_alias.rb b/app/models/account_alias.rb
index 792e9e8d4d..3d659142a0 100644
--- a/app/models/account_alias.rb
+++ b/app/models/account_alias.rb
@@ -33,7 +33,7 @@ class AccountAlias < ApplicationRecord
def set_uri
target_account = ResolveAccountService.new.call(acct)
self.uri = ActivityPub::TagManager.instance.uri_for(target_account) unless target_account.nil?
- rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
+ rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
# Validation will take care of it
end
diff --git a/app/models/account_migration.rb b/app/models/account_migration.rb
index 681b5b2cd0..4fae98ed72 100644
--- a/app/models/account_migration.rb
+++ b/app/models/account_migration.rb
@@ -54,7 +54,7 @@ class AccountMigration < ApplicationRecord
def set_target_account
self.target_account = ResolveAccountService.new.call(acct)
- rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
+ rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
# Validation will take care of it
end
diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb
index 427ebdae29..e2c4b8acf5 100644
--- a/app/models/concerns/account_interactions.rb
+++ b/app/models/concerns/account_interactions.rb
@@ -131,9 +131,12 @@ module AccountInteractions
.find_or_create_by!(target_account: other_account)
end
- def mute!(other_account, notifications: nil)
+ def mute!(other_account, notifications: nil, duration: 0)
notifications = true if notifications.nil?
- mute = mute_relationships.create_with(hide_notifications: notifications).find_or_create_by!(target_account: other_account)
+ mute = mute_relationships.create_with(hide_notifications: notifications).find_or_initialize_by(target_account: other_account)
+ mute.expires_in = duration.zero? ? nil : duration
+ mute.save!
+
remove_potential_friendship(other_account)
# When toggling a mute between hiding and allowing notifications, the mute will already exist, so the find_or_create_by! call will return the existing Mute without updating the hide_notifications attribute. Therefore, we check that hide_notifications? is what we want and set it if it isn't.
@@ -240,6 +243,26 @@ module AccountInteractions
.where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago)
end
+ def remote_followers_hash(url_prefix)
+ Rails.cache.fetch("followers_hash:#{id}:#{url_prefix}") do
+ digest = "\x00" * 32
+ followers.where(Account.arel_table[:uri].matches(url_prefix + '%', false, true)).pluck_each(:uri) do |uri|
+ Xorcist.xor!(digest, Digest::SHA256.digest(uri))
+ end
+ digest.unpack('H*')[0]
+ end
+ end
+
+ def local_followers_hash
+ Rails.cache.fetch("followers_hash:#{id}:local") do
+ digest = "\x00" * 32
+ followers.where(domain: nil).pluck_each(:username) do |username|
+ Xorcist.xor!(digest, Digest::SHA256.digest(ActivityPub::TagManager.instance.uri_for_username(username)))
+ end
+ digest.unpack('H*')[0]
+ end
+ end
+
private
def remove_potential_friendship(other_account, mutual = false)
diff --git a/app/models/concerns/expireable.rb b/app/models/concerns/expireable.rb
index f7d2bab498..a66a4661b1 100644
--- a/app/models/concerns/expireable.rb
+++ b/app/models/concerns/expireable.rb
@@ -6,7 +6,15 @@ module Expireable
included do
scope :expired, -> { where.not(expires_at: nil).where('expires_at < ?', Time.now.utc) }
- attr_reader :expires_in
+ def expires_in
+ return @expires_in if defined?(@expires_in)
+
+ if expires_at.nil?
+ nil
+ else
+ (expires_at - created_at).to_i
+ end
+ end
def expires_in=(interval)
self.expires_at = interval.to_i.seconds.from_now if interval.present?
diff --git a/app/models/follow.rb b/app/models/follow.rb
index 0b4ddbf3f8..55a9da7928 100644
--- a/app/models/follow.rb
+++ b/app/models/follow.rb
@@ -41,8 +41,10 @@ class Follow < ApplicationRecord
before_validation :set_uri, only: :create
after_create :increment_cache_counters
+ after_create :invalidate_hash_cache
after_destroy :remove_endorsements
after_destroy :decrement_cache_counters
+ after_destroy :invalidate_hash_cache
private
@@ -63,4 +65,10 @@ class Follow < ApplicationRecord
account&.decrement_count!(:following_count)
target_account&.decrement_count!(:followers_count)
end
+
+ def invalidate_hash_cache
+ return if account.local? && target_account.local?
+
+ Rails.cache.delete("followers_hash:#{target_account_id}:#{account.synchronization_uri_prefix}")
+ end
end
diff --git a/app/models/form/ip_block_batch.rb b/app/models/form/ip_block_batch.rb
new file mode 100644
index 0000000000..f6fe9b5935
--- /dev/null
+++ b/app/models/form/ip_block_batch.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class Form::IpBlockBatch
+ include ActiveModel::Model
+ include Authorization
+ include AccountableConcern
+
+ attr_accessor :ip_block_ids, :action, :current_account
+
+ def save
+ case action
+ when 'delete'
+ delete!
+ end
+ end
+
+ private
+
+ def ip_blocks
+ @ip_blocks ||= IpBlock.where(id: ip_block_ids)
+ end
+
+ def delete!
+ ip_blocks.each { |ip_block| authorize(ip_block, :destroy?) }
+
+ ip_blocks.each do |ip_block|
+ ip_block.destroy
+ log_action :destroy, ip_block
+ end
+ end
+end
diff --git a/app/models/form/redirect.rb b/app/models/form/redirect.rb
index a7961f8e8a..19ee9faedd 100644
--- a/app/models/form/redirect.rb
+++ b/app/models/form/redirect.rb
@@ -32,7 +32,7 @@ class Form::Redirect
def set_target_account
@target_account = ResolveAccountService.new.call(acct)
- rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
+ rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
# Validation will take care of it
end
diff --git a/app/models/ip_block.rb b/app/models/ip_block.rb
new file mode 100644
index 0000000000..aedd3ca0d4
--- /dev/null
+++ b/app/models/ip_block.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+# == Schema Information
+#
+# Table name: ip_blocks
+#
+# id :bigint(8) not null, primary key
+# created_at :datetime not null
+# updated_at :datetime not null
+# expires_at :datetime
+# ip :inet default(#), not null
+# severity :integer default(NULL), not null
+# comment :text default(""), not null
+#
+
+class IpBlock < ApplicationRecord
+ CACHE_KEY = 'blocked_ips'
+
+ include Expireable
+
+ enum severity: {
+ sign_up_requires_approval: 5000,
+ no_access: 9999,
+ }
+
+ validates :ip, :severity, presence: true
+
+ after_commit :reset_cache
+
+ class << self
+ def blocked?(remote_ip)
+ blocked_ips_map = Rails.cache.fetch(CACHE_KEY) { FastIpMap.new(IpBlock.where(severity: :no_access).pluck(:ip)) }
+ blocked_ips_map.include?(remote_ip)
+ end
+ end
+
+ private
+
+ def reset_cache
+ Rails.cache.delete(CACHE_KEY)
+ end
+end
diff --git a/app/models/mute.rb b/app/models/mute.rb
index 639120f7dd..fe8b6f42c8 100644
--- a/app/models/mute.rb
+++ b/app/models/mute.rb
@@ -9,11 +9,14 @@
# hide_notifications :boolean default(TRUE), not null
# account_id :bigint(8) not null
# target_account_id :bigint(8) not null
+# hide_notifications :boolean default(TRUE), not null
+# expires_at :datetime
#
class Mute < ApplicationRecord
include Paginable
include RelationshipCacheable
+ include Expireable
belongs_to :account
belongs_to :target_account, class_name: 'Account'
diff --git a/app/models/remote_follow.rb b/app/models/remote_follow.rb
index 30b84f7d52..911c067133 100644
--- a/app/models/remote_follow.rb
+++ b/app/models/remote_follow.rb
@@ -56,7 +56,7 @@ class RemoteFollow
if domain.nil?
@addressable_template = Addressable::Template.new("#{authorize_interaction_url}?uri={uri}")
- elsif redirect_url_link.nil? || redirect_url_link.template.nil?
+ elsif redirect_uri_template.nil?
missing_resource_error
else
@addressable_template = Addressable::Template.new(redirect_uri_template)
@@ -64,16 +64,12 @@ class RemoteFollow
end
def redirect_uri_template
- redirect_url_link.template
- end
-
- def redirect_url_link
- acct_resource&.link('http://ostatus.org/schema/1.0/subscribe')
+ acct_resource&.link('http://ostatus.org/schema/1.0/subscribe', 'template')
end
def acct_resource
@acct_resource ||= webfinger!("acct:#{acct}")
- rescue Goldfinger::Error, HTTP::ConnectionError
+ rescue Webfinger::Error, HTTP::ConnectionError
nil
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 6cd2ca6bdc..3dcfd820e0 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -41,6 +41,7 @@
# sign_in_token :string
# sign_in_token_sent_at :datetime
# webauthn_id :string
+# sign_up_ip :inet
#
class User < ApplicationRecord
@@ -97,7 +98,7 @@ class User < ApplicationRecord
scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) }
scope :active, -> { confirmed.where(arel_table[:current_sign_in_at].gteq(ACTIVE_DURATION.ago)).joins(:account).where(accounts: { suspended_at: nil }) }
scope :matches_email, ->(value) { where(arel_table[:email].matches("#{value}%")) }
- scope :matches_ip, ->(value) { left_joins(:session_activations).where('users.current_sign_in_ip <<= ?', value).or(left_joins(:session_activations).where('users.last_sign_in_ip <<= ?', value)).or(left_joins(:session_activations).where('session_activations.ip <<= ?', value)) }
+ scope :matches_ip, ->(value) { left_joins(:session_activations).where('users.current_sign_in_ip <<= ?', value).or(left_joins(:session_activations).where('users.sign_up_ip <<= ?', value)).or(left_joins(:session_activations).where('users.last_sign_in_ip <<= ?', value)).or(left_joins(:session_activations).where('session_activations.ip <<= ?', value)) }
scope :emailable, -> { confirmed.enabled.joins(:account).merge(Account.searchable) }
before_validation :sanitize_languages
@@ -115,7 +116,7 @@ class User < ApplicationRecord
:reduce_motion, :system_font_ui, :noindex, :flavour, :skin, :display_media, :hide_network, :hide_followers_count,
:expand_spoilers, :default_language, :aggregate_reblogs, :show_application,
:advanced_layout, :use_blurhash, :use_pending_items, :trends, :crop_images,
- :default_content_type, :system_emoji_font,
+ :disable_swiping, :default_content_type, :system_emoji_font,
to: :settings, prefix: :setting, allow_nil: false
attr_reader :invite_code, :sign_in_token_attempt
@@ -331,6 +332,7 @@ class User < ApplicationRecord
arr << [current_sign_in_at, current_sign_in_ip] if current_sign_in_ip.present?
arr << [last_sign_in_at, last_sign_in_ip] if last_sign_in_ip.present?
+ arr << [created_at, sign_up_ip] if sign_up_ip.present?
arr.sort_by { |pair| pair.first || Time.now.utc }.uniq(&:last).reverse!
end
@@ -385,7 +387,17 @@ class User < ApplicationRecord
end
def set_approved
- self.approved = open_registrations? || valid_invitation? || external?
+ self.approved = begin
+ if sign_up_from_ip_requires_approval?
+ false
+ else
+ open_registrations? || valid_invitation? || external?
+ end
+ end
+ end
+
+ def sign_up_from_ip_requires_approval?
+ !sign_up_ip.nil? && IpBlock.where(severity: :sign_up_requires_approval).where('ip >>= ?', sign_up_ip.to_s).exists?
end
def open_registrations?
diff --git a/app/policies/ip_block_policy.rb b/app/policies/ip_block_policy.rb
new file mode 100644
index 0000000000..34dbd746a3
--- /dev/null
+++ b/app/policies/ip_block_policy.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class IpBlockPolicy < ApplicationPolicy
+ def index?
+ admin?
+ end
+
+ def create?
+ admin?
+ end
+
+ def destroy?
+ admin?
+ end
+end
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index c520c9bcbe..470cec8a1f 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -48,6 +48,7 @@ class InitialStateSerializer < ActiveModel::Serializer
store[:display_media] = object.current_account.user.setting_display_media
store[:expand_spoilers] = object.current_account.user.setting_expand_spoilers
store[:reduce_motion] = object.current_account.user.setting_reduce_motion
+ store[:disable_swiping] = object.current_account.user.setting_disable_swiping
store[:advanced_layout] = object.current_account.user.setting_advanced_layout
store[:use_blurhash] = object.current_account.user.setting_use_blurhash
store[:use_pending_items] = object.current_account.user.setting_use_pending_items
diff --git a/app/serializers/rest/muted_account_serializer.rb b/app/serializers/rest/muted_account_serializer.rb
new file mode 100644
index 0000000000..3ddd706dcd
--- /dev/null
+++ b/app/serializers/rest/muted_account_serializer.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class REST::MutedAccountSerializer < REST::AccountSerializer
+ attribute :mute_expires_at
+
+ def mute_expires_at
+ mute = current_user.account.mute_relationships.find_by(target_account_id: object.id)
+ mute && !mute.expired? ? mute.expires_at : nil
+ end
+end
diff --git a/app/services/activitypub/fetch_remote_account_service.rb b/app/services/activitypub/fetch_remote_account_service.rb
index 83fbf6d07d..e5bd0c47c8 100644
--- a/app/services/activitypub/fetch_remote_account_service.rb
+++ b/app/services/activitypub/fetch_remote_account_service.rb
@@ -39,17 +39,16 @@ class ActivityPub::FetchRemoteAccountService < BaseService
webfinger = webfinger!("acct:#{@username}@#{@domain}")
confirmed_username, confirmed_domain = split_acct(webfinger.subject)
- return webfinger.link('self')&.href == @uri if @username.casecmp(confirmed_username).zero? && @domain.casecmp(confirmed_domain).zero?
+ return webfinger.link('self', 'href') == @uri if @username.casecmp(confirmed_username).zero? && @domain.casecmp(confirmed_domain).zero?
webfinger = webfinger!("acct:#{confirmed_username}@#{confirmed_domain}")
@username, @domain = split_acct(webfinger.subject)
- self_reference = webfinger.link('self')
return false unless @username.casecmp(confirmed_username).zero? && @domain.casecmp(confirmed_domain).zero?
- return false if self_reference&.href != @uri
+ return false if webfinger.link('self', 'href') != @uri
true
- rescue Goldfinger::Error
+ rescue Webfinger::Error
false
end
diff --git a/app/services/activitypub/prepare_followers_synchronization_service.rb b/app/services/activitypub/prepare_followers_synchronization_service.rb
new file mode 100644
index 0000000000..2d22ed701e
--- /dev/null
+++ b/app/services/activitypub/prepare_followers_synchronization_service.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ActivityPub::PrepareFollowersSynchronizationService < BaseService
+ include JsonLdHelper
+
+ def call(account, params)
+ @account = account
+
+ return if params['collectionId'] != @account.followers_url || invalid_origin?(params['url']) || @account.local_followers_hash == params['digest']
+
+ ActivityPub::FollowersSynchronizationWorker.perform_async(@account.id, params['url'])
+ end
+end
diff --git a/app/services/activitypub/synchronize_followers_service.rb b/app/services/activitypub/synchronize_followers_service.rb
new file mode 100644
index 0000000000..d83fcf55e6
--- /dev/null
+++ b/app/services/activitypub/synchronize_followers_service.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+class ActivityPub::SynchronizeFollowersService < BaseService
+ include JsonLdHelper
+ include Payloadable
+
+ def call(account, partial_collection_url)
+ @account = account
+
+ items = collection_items(partial_collection_url)
+ return if items.nil?
+
+ # There could be unresolved accounts (hence the call to .compact) but this
+ # should never happen in practice, since in almost all cases we keep an
+ # Account record, and should we not do that, we should have sent a Delete.
+ # In any case there is not much we can do if that occurs.
+ @expected_followers = items.map { |uri| ActivityPub::TagManager.instance.uri_to_resource(uri, Account) }.compact
+
+ remove_unexpected_local_followers!
+ handle_unexpected_outgoing_follows!
+ end
+
+ private
+
+ def remove_unexpected_local_followers!
+ @account.followers.local.where.not(id: @expected_followers.map(&:id)).each do |unexpected_follower|
+ UnfollowService.new.call(unexpected_follower, @account)
+ end
+ end
+
+ def handle_unexpected_outgoing_follows!
+ @expected_followers.each do |expected_follower|
+ next if expected_follower.following?(@account)
+
+ if expected_follower.requested?(@account)
+ # For some reason the follow request went through but we missed it
+ expected_follower.follow_requests.find_by(target_account: @account)&.authorize!
+ else
+ # Since we were not aware of the follow from our side, we do not have an
+ # ID for it that we can include in the Undo activity. For this reason,
+ # the Undo may not work with software that relies exclusively on
+ # matching activity IDs and not the actor and target
+ follow = Follow.new(account: expected_follower, target_account: @account)
+ ActivityPub::DeliveryWorker.perform_async(build_undo_follow_json(follow), follow.account_id, follow.target_account.inbox_url)
+ end
+ end
+ end
+
+ def build_undo_follow_json(follow)
+ Oj.dump(serialize_payload(follow, ActivityPub::UndoFollowSerializer))
+ end
+
+ def collection_items(collection_or_uri)
+ collection = fetch_collection(collection_or_uri)
+ return unless collection.is_a?(Hash)
+
+ collection = fetch_collection(collection['first']) if collection['first'].present?
+ return unless collection.is_a?(Hash)
+
+ case collection['type']
+ when 'Collection', 'CollectionPage'
+ collection['items']
+ when 'OrderedCollection', 'OrderedCollectionPage'
+ collection['orderedItems']
+ end
+ end
+
+ def fetch_collection(collection_or_uri)
+ return collection_or_uri if collection_or_uri.is_a?(Hash)
+ return if invalid_origin?(collection_or_uri)
+
+ fetch_resource_without_id_validation(collection_or_uri, nil, true)
+ end
+end
diff --git a/app/services/app_sign_up_service.rb b/app/services/app_sign_up_service.rb
index c9739c77d1..e006941577 100644
--- a/app/services/app_sign_up_service.rb
+++ b/app/services/app_sign_up_service.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
class AppSignUpService < BaseService
- def call(app, params)
+ def call(app, remote_ip, params)
return unless allowed_registrations?
user_params = params.slice(:email, :password, :agreement, :locale)
account_params = params.slice(:username)
invite_request_params = { text: params[:reason] }
- user = User.create!(user_params.merge(created_by_application: app, password_confirmation: user_params[:password], account_attributes: account_params, invite_request_attributes: invite_request_params))
+ user = User.create!(user_params.merge(created_by_application: app, sign_up_ip: remote_ip, password_confirmation: user_params[:password], account_attributes: account_params, invite_request_attributes: invite_request_params))
Doorkeeper::AccessToken.create!(application: app,
resource_owner_id: user.id,
diff --git a/app/services/mute_service.rb b/app/services/mute_service.rb
index 676804cb99..9ae9afd623 100644
--- a/app/services/mute_service.rb
+++ b/app/services/mute_service.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
class MuteService < BaseService
- def call(account, target_account, notifications: nil)
+ def call(account, target_account, notifications: nil, duration: 0)
return if account.id == target_account.id
- mute = account.mute!(target_account, notifications: notifications)
+ mute = account.mute!(target_account, notifications: notifications, duration: duration)
if mute.hide_notifications?
BlockWorker.perform_async(account.id, target_account.id)
@@ -12,6 +12,8 @@ class MuteService < BaseService
MuteWorker.perform_async(account.id, target_account.id)
end
+ DeleteMuteWorker.perform_at(duration.seconds, mute.id) if duration != 0
+
mute
end
end
diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb
index feffb872be..d5ea69da13 100644
--- a/app/services/process_mentions_service.rb
+++ b/app/services/process_mentions_service.rb
@@ -29,7 +29,7 @@ class ProcessMentionsService < BaseService
if mention_undeliverable?(mentioned_account)
begin
mentioned_account = resolve_account_service.call(Regexp.last_match(1))
- rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::UnexpectedResponseError
+ rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::UnexpectedResponseError
mentioned_account = nil
end
end
diff --git a/app/services/resolve_account_service.rb b/app/services/resolve_account_service.rb
index ba77552c6c..3f7bb7cc52 100644
--- a/app/services/resolve_account_service.rb
+++ b/app/services/resolve_account_service.rb
@@ -26,11 +26,10 @@ class ResolveAccountService < BaseService
@account ||= Account.find_remote(@username, @domain)
- return @account if @account&.local? || !webfinger_update_due?
+ return @account if @account&.local? || @domain.nil? || !webfinger_update_due?
# At this point we are in need of a Webfinger query, which may
# yield us a different username/domain through a redirect
-
process_webfinger!(@uri)
# Because the username/domain pair may be different than what
@@ -47,7 +46,7 @@ class ResolveAccountService < BaseService
# either needs to be created, or updated from fresh data
process_account!
- rescue Goldfinger::Error, WebfingerRedirectError, Oj::ParseError => e
+ rescue Webfinger::Error, WebfingerRedirectError, Oj::ParseError => e
Rails.logger.debug "Webfinger query for #{@uri} failed: #{e}"
nil
end
@@ -118,11 +117,11 @@ class ResolveAccountService < BaseService
end
def activitypub_ready?
- !@webfinger.link('self').nil? && ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(@webfinger.link('self').type)
+ ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(@webfinger.link('self', 'type'))
end
def actor_url
- @actor_url ||= @webfinger.link('self').href
+ @actor_url ||= @webfinger.link('self', 'href')
end
def actor_json
diff --git a/app/views/admin/ip_blocks/_ip_block.html.haml b/app/views/admin/ip_blocks/_ip_block.html.haml
new file mode 100644
index 0000000000..e07e2b4448
--- /dev/null
+++ b/app/views/admin/ip_blocks/_ip_block.html.haml
@@ -0,0 +1,11 @@
+.batch-table__row
+ %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox
+ = f.check_box :ip_block_ids, { multiple: true, include_hidden: false }, ip_block.id
+ .batch-table__row__content
+ .batch-table__row__content__text
+ %samp= "#{ip_block.ip}/#{ip_block.ip.prefix}"
+ - if ip_block.comment.present?
+ •
+ = ip_block.comment
+ %br/
+ = t("simple_form.labels.ip_block.severities.#{ip_block.severity}")
diff --git a/app/views/admin/ip_blocks/index.html.haml b/app/views/admin/ip_blocks/index.html.haml
new file mode 100644
index 0000000000..a282a4cfef
--- /dev/null
+++ b/app/views/admin/ip_blocks/index.html.haml
@@ -0,0 +1,28 @@
+- content_for :page_title do
+ = t('admin.ip_blocks.title')
+
+- content_for :header_tags do
+ = javascript_pack_tag 'admin', integrity: true, async: true, crossorigin: 'anonymous'
+
+- if can?(:create, :ip_block)
+ - content_for :heading_actions do
+ = link_to t('admin.ip_blocks.add_new'), new_admin_ip_block_path, class: 'button'
+
+= form_for(@form, url: batch_admin_ip_blocks_path) do |f|
+ = hidden_field_tag :page, params[:page] || 1
+
+ .batch-table
+ .batch-table__toolbar
+ %label.batch-table__toolbar__select.batch-checkbox-all
+ = check_box_tag :batch_checkbox_all, nil, false
+ .batch-table__toolbar__actions
+ - if can?(:destroy, :ip_block)
+ = f.button safe_join([fa_icon('times'), t('admin.ip_blocks.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+ .batch-table__body
+ - if @ip_blocks.empty?
+ = nothing_here 'nothing-here--under-tabs'
+ - else
+ = render partial: 'ip_block', collection: @ip_blocks, locals: { f: f }
+
+= paginate @ip_blocks
+
diff --git a/app/views/admin/ip_blocks/new.html.haml b/app/views/admin/ip_blocks/new.html.haml
new file mode 100644
index 0000000000..69f6b98b9b
--- /dev/null
+++ b/app/views/admin/ip_blocks/new.html.haml
@@ -0,0 +1,20 @@
+- content_for :page_title do
+ = t('.title')
+
+= simple_form_for @ip_block, url: admin_ip_blocks_path do |f|
+ = render 'shared/error_messages', object: @ip_block
+
+ .fields-group
+ = f.input :ip, as: :string, wrapper: :with_block_label, input_html: { placeholder: '192.0.2.0/24' }
+
+ .fields-group
+ = f.input :expires_in, wrapper: :with_block_label, collection: [1.day, 2.weeks, 1.month, 6.months, 1.year, 3.years].map(&:to_i), label_method: lambda { |i| I18n.t("admin.ip_blocks.expires_in.#{i}") }, prompt: I18n.t('invites.expires_in_prompt')
+
+ .fields-group
+ = f.input :severity, as: :radio_buttons, collection: IpBlock.severities.keys, include_blank: false, wrapper: :with_block_label, label_method: lambda { |severity| safe_join([I18n.t("simple_form.labels.ip_block.severities.#{severity}"), content_tag(:span, I18n.t("simple_form.hints.ip_block.severities.#{severity}"), class: 'hint')]) }
+
+ .fields-group
+ = f.input :comment, as: :string, wrapper: :with_block_label
+
+ .actions
+ = f.button :button, t('admin.ip_blocks.add_new'), type: :submit
diff --git a/app/views/admin/pending_accounts/_account.html.haml b/app/views/admin/pending_accounts/_account.html.haml
index 7a9796a674..5b475b59a9 100644
--- a/app/views/admin/pending_accounts/_account.html.haml
+++ b/app/views/admin/pending_accounts/_account.html.haml
@@ -7,7 +7,7 @@
%strong= account.user_email
= "(@#{account.username})"
%br/
- = account.user_current_sign_in_ip
+ %samp= account.user_current_sign_in_ip
•
= t 'admin.accounts.time_in_queue', time: time_ago_in_words(account.user&.created_at)
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 3336cf3910..1481f69734 100755
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -6,6 +6,7 @@
- if cdn_host?
%link{ rel: 'dns-prefetch', href: cdn_host }/
+ %meta{ name: 'cdn-host', content: cdn_host }/
- if storage_host?
%link{ rel: 'dns-prefetch', href: storage_host }/
diff --git a/app/views/layouts/embedded.html.haml b/app/views/layouts/embedded.html.haml
index 75441b452f..69b206f695 100644
--- a/app/views/layouts/embedded.html.haml
+++ b/app/views/layouts/embedded.html.haml
@@ -6,6 +6,7 @@
- if cdn_host?
%link{ rel: 'dns-prefetch', href: cdn_host }/
+ %meta{ name: 'cdn-host', content: cdn_host }/
- if storage_host?
%link{ rel: 'dns-prefetch', href: storage_host }/
diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml
index 5fc865814a..ccea2e9b7f 100644
--- a/app/views/settings/preferences/appearance/show.html.haml
+++ b/app/views/settings/preferences/appearance/show.html.haml
@@ -27,6 +27,7 @@
.fields-group
= f.input :setting_auto_play_gif, as: :boolean, wrapper: :with_label, recommended: true
= f.input :setting_reduce_motion, as: :boolean, wrapper: :with_label
+ = f.input :setting_disable_swiping, as: :boolean, wrapper: :with_label
= f.input :setting_system_font_ui, as: :boolean, wrapper: :with_label
= f.input :setting_system_emoji_font, as: :boolean, wrapper: :with_label
diff --git a/app/views/statuses/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml
index f2b6866e91..0e5ca41d18 100644
--- a/app/views/statuses/_simple_status.html.haml
+++ b/app/views/statuses/_simple_status.html.haml
@@ -52,13 +52,12 @@
= t 'statuses.show_thread'
.status__action-bar
- .status__action-bar__counter
- = link_to remote_interaction_path(status, type: :reply), class: 'status__action-bar-button icon-button modal-button' do
- - if status.in_reply_to_id.nil?
- = fa_icon 'reply fw'
- - else
- = fa_icon 'reply-all fw'
- .status__action-bar__counter__label= obscured_counter status.replies_count
+ = link_to remote_interaction_path(status, type: :reply), class: 'status__action-bar-button icon-button icon-button--with-counter modal-button' do
+ - if status.in_reply_to_id.nil?
+ = fa_icon 'reply fw'
+ - else
+ = fa_icon 'reply-all fw'
+ %span.icon-button__counter= obscured_counter status.replies_count
= link_to remote_interaction_path(status, type: :reblog), class: 'status__action-bar-button icon-button modal-button' do
- if status.distributable?
= fa_icon 'retweet fw'
diff --git a/app/views/well_known/host_meta/show.xml.ruby b/app/views/well_known/host_meta/show.xml.ruby
index 0a6bdc322f..b4e867c5f8 100644
--- a/app/views/well_known/host_meta/show.xml.ruby
+++ b/app/views/well_known/host_meta/show.xml.ruby
@@ -5,7 +5,6 @@ doc << Ox::Element.new('XRD').tap do |xrd|
xrd << Ox::Element.new('Link').tap do |link|
link['rel'] = 'lrdd'
- link['type'] = 'application/xrd+xml'
link['template'] = @webfinger_template
end
end
diff --git a/app/workers/activitypub/delivery_worker.rb b/app/workers/activitypub/delivery_worker.rb
index 60775787a8..6c5a576a70 100644
--- a/app/workers/activitypub/delivery_worker.rb
+++ b/app/workers/activitypub/delivery_worker.rb
@@ -2,6 +2,7 @@
class ActivityPub::DeliveryWorker
include Sidekiq::Worker
+ include RoutingHelper
include JsonLdHelper
STOPLIGHT_FAILURE_THRESHOLD = 10
@@ -38,9 +39,18 @@ class ActivityPub::DeliveryWorker
Request.new(:post, @inbox_url, body: @json, http_client: http_client).tap do |request|
request.on_behalf_of(@source_account, :uri, sign_with: @options[:sign_with])
request.add_headers(HEADERS)
+ request.add_headers({ 'Collection-Synchronization' => synchronization_header }) if ENV['DISABLE_FOLLOWERS_SYNCHRONIZATION'] != 'true' && @options[:synchronize_followers]
end
end
+ def synchronization_header
+ "collectionId=\"#{account_followers_url(@source_account)}\", digest=\"#{@source_account.remote_followers_hash(inbox_url_prefix)}\", url=\"#{account_followers_synchronization_url(@source_account)}\""
+ end
+
+ def inbox_url_prefix
+ @inbox_url[/http(s?):\/\/[^\/]+\//]
+ end
+
def perform_request
light = Stoplight(@inbox_url) do
request_pool.with(@host) do |http_client|
diff --git a/app/workers/activitypub/distribution_worker.rb b/app/workers/activitypub/distribution_worker.rb
index e4997ba0ea..9b4814644f 100644
--- a/app/workers/activitypub/distribution_worker.rb
+++ b/app/workers/activitypub/distribution_worker.rb
@@ -13,7 +13,7 @@ class ActivityPub::DistributionWorker
return if skip_distribution?
ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url|
- [payload, @account.id, inbox_url]
+ [payload, @account.id, inbox_url, { synchronize_followers: !@status.distributable? }]
end
relay! if relayable?
diff --git a/app/workers/activitypub/followers_synchronization_worker.rb b/app/workers/activitypub/followers_synchronization_worker.rb
new file mode 100644
index 0000000000..35a3ef0b96
--- /dev/null
+++ b/app/workers/activitypub/followers_synchronization_worker.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class ActivityPub::FollowersSynchronizationWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: 'push', lock: :until_executed
+
+ def perform(account_id, url)
+ @account = Account.find_by(id: account_id)
+ return true if @account.nil?
+
+ ActivityPub::SynchronizeFollowersService.new.call(@account, url)
+ end
+end
diff --git a/app/workers/delete_mute_worker.rb b/app/workers/delete_mute_worker.rb
new file mode 100644
index 0000000000..eb031020e1
--- /dev/null
+++ b/app/workers/delete_mute_worker.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class DeleteMuteWorker
+ include Sidekiq::Worker
+
+ def perform(mute_id)
+ mute = Mute.find_by(id: mute_id)
+ UnmuteService.new.call(mute.account, mute.target_account) if mute&.expired?
+ end
+end
diff --git a/app/workers/scheduler/ip_cleanup_scheduler.rb b/app/workers/scheduler/ip_cleanup_scheduler.rb
index 6d38b52a29..853f20e251 100644
--- a/app/workers/scheduler/ip_cleanup_scheduler.rb
+++ b/app/workers/scheduler/ip_cleanup_scheduler.rb
@@ -3,13 +3,23 @@
class Scheduler::IpCleanupScheduler
include Sidekiq::Worker
- RETENTION_PERIOD = 1.year
+ IP_RETENTION_PERIOD = 1.year.freeze
sidekiq_options lock: :until_executed, retry: 0
def perform
- time_ago = RETENTION_PERIOD.ago
- SessionActivation.where('updated_at < ?', time_ago).in_batches.destroy_all
- User.where('last_sign_in_at < ?', time_ago).where.not(last_sign_in_ip: nil).in_batches.update_all(last_sign_in_ip: nil)
+ clean_ip_columns!
+ clean_expired_ip_blocks!
+ end
+
+ private
+
+ def clean_ip_columns!
+ SessionActivation.where('updated_at < ?', IP_RETENTION_PERIOD.ago).in_batches.destroy_all
+ User.where('current_sign_in_at < ?', IP_RETENTION_PERIOD.ago).in_batches.update_all(last_sign_in_ip: nil, current_sign_in_ip: nil, sign_up_ip: nil)
+ end
+
+ def clean_expired_ip_blocks!
+ IpBlock.expired.in_batches.destroy_all
end
end
diff --git a/chart/templates/cronjob-media-remove.yaml b/chart/templates/cronjob-media-remove.yaml
new file mode 100644
index 0000000000..5d78f3395c
--- /dev/null
+++ b/chart/templates/cronjob-media-remove.yaml
@@ -0,0 +1,73 @@
+{{ if .Values.cron.removeMedia.enabled }}
+apiVersion: batch/v1beta1
+kind: CronJob
+metadata:
+ name: {{ include "mastodon.fullname" . }}-media-remove
+ labels:
+ {{- include "mastodon.labels" . | nindent 4 }}
+spec:
+ schedule: {{ .Values.cron.removeMedia.schedule }}
+ jobTemplate:
+ spec:
+ template:
+ metadata:
+ name: {{ include "mastodon.fullname" . }}-media-remove
+ spec:
+ restartPolicy: OnFailure
+ # ensure we run on the same node as the other rails components; only
+ # required when using PVCs that are ReadWriteOnce
+ {{- if or (eq "ReadWriteOnce" .Values.persistence.assets.accessMode) (eq "ReadWriteOnce" .Values.persistence.system.accessMode) }}
+ affinity:
+ podAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ - labelSelector:
+ matchExpressions:
+ - key: component
+ operator: In
+ values:
+ - rails
+ topologyKey: kubernetes.io/hostname
+ {{- end }}
+ volumes:
+ - name: assets
+ persistentVolumeClaim:
+ claimName: {{ template "mastodon.fullname" . }}-assets
+ - name: system
+ persistentVolumeClaim:
+ claimName: {{ template "mastodon.fullname" . }}-system
+ containers:
+ - name: {{ include "mastodon.fullname" . }}-media-remove
+ image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+ imagePullPolicy: {{ .Values.image.pullPolicy }}
+ command:
+ - bin/tootctl
+ - media
+ - remove
+ envFrom:
+ - configMapRef:
+ name: {{ include "mastodon.fullname" . }}-env
+ - secretRef:
+ name: {{ template "mastodon.fullname" . }}
+ env:
+ - name: "DB_PASS"
+ valueFrom:
+ secretKeyRef:
+ {{- if .Values.postgresql.enabled }}
+ name: {{ .Release.Name }}-postgresql
+ {{- else }}
+ name: {{ template "mastodon.fullname" . }}
+ {{- end }}
+ key: postgresql-password
+ - name: "REDIS_PASSWORD"
+ valueFrom:
+ secretKeyRef:
+ name: {{ .Release.Name }}-redis
+ key: redis-password
+ - name: "PORT"
+ value: {{ .Values.application.web.port | quote }}
+ volumeMounts:
+ - name: assets
+ mountPath: /opt/mastodon/public/assets
+ - name: system
+ mountPath: /opt/mastodon/public/system
+{{- end }}
diff --git a/chart/values.yaml.template b/chart/values.yaml.template
index ff680b81f7..c18d2f0b5b 100644
--- a/chart/values.yaml.template
+++ b/chart/values.yaml.template
@@ -39,6 +39,12 @@ createAdmin:
# available locales: https://github.com/tootsuite/mastodon/blob/master/config/application.rb#L43
locale: en
+cron:
+ # run `tootctl media remove` every week
+ removeMedia:
+ enabled: true
+ schedule: "0 0 * * 0"
+
application:
web:
port: 3000
diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb
index b4849370db..b841d52203 100644
--- a/config/initializers/paperclip.rb
+++ b/config/initializers/paperclip.rb
@@ -62,7 +62,7 @@ if ENV['S3_ENABLED'] == 'true'
s3_options: {
signature_version: ENV.fetch('S3_SIGNATURE_VERSION') { 'v4' },
http_open_timeout: ENV.fetch('S3_OPEN_TIMEOUT'){ '5' }.to_i,
- http_read_timeout: 5,
+ http_read_timeout: ENV.fetch('S3_READ_TIMEOUT'){ '5' }.to_i,
http_idle_timeout: 5,
retry_limit: 0,
}
diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb
index cd29afac52..6662ef40b0 100644
--- a/config/initializers/rack_attack.rb
+++ b/config/initializers/rack_attack.rb
@@ -42,6 +42,10 @@ class Rack::Attack
req.remote_ip == '127.0.0.1' || req.remote_ip == '::1'
end
+ Rack::Attack.blocklist('deny from blocklist') do |req|
+ IpBlock.blocked?(req.remote_ip)
+ end
+
throttle('throttle_authenticated_api', limit: 300, period: 5.minutes) do |req|
req.authenticated_user_id if req.api_request?
end
diff --git a/config/initializers/twitter_regex.rb b/config/initializers/twitter_regex.rb
index f84f7c0cbb..7f99a00056 100644
--- a/config/initializers/twitter_regex.rb
+++ b/config/initializers/twitter_regex.rb
@@ -29,7 +29,7 @@ module Twitter
( # $1 total match
(#{REGEXEN[:valid_url_preceding_chars]}) # $2 Preceding character
( # $3 URL
- ((?:https?|dat|dweb|ipfs|ipns|ssb|gopher):\/\/)? # $4 Protocol (optional)
+ ((?:https?|dat|dweb|ipfs|ipns|ssb|gopher|gemini):\/\/)? # $4 Protocol (optional)
(#{REGEXEN[:valid_domain]}) # $5 Domain(s)
(?::(#{REGEXEN[:valid_port_number]}))? # $6 Port number (optional)
(/#{REGEXEN[:valid_url_path]}*)? # $7 URL Path and anchor
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 427b2c3fca..084006a2af 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -223,12 +223,14 @@ en:
create_domain_allow: Create Domain Allow
create_domain_block: Create Domain Block
create_email_domain_block: Create E-mail Domain Block
+ create_ip_block: Create IP rule
demote_user: Demote User
destroy_announcement: Delete Announcement
destroy_custom_emoji: Delete Custom Emoji
destroy_domain_allow: Delete Domain Allow
destroy_domain_block: Delete Domain Block
destroy_email_domain_block: Delete e-mail domain block
+ destroy_ip_block: Delete IP rule
destroy_status: Delete Status
disable_2fa_user: Disable 2FA
disable_custom_emoji: Disable Custom Emoji
@@ -259,12 +261,14 @@ en:
create_domain_allow: "%{name} allowed federation with domain %{target}"
create_domain_block: "%{name} blocked domain %{target}"
create_email_domain_block: "%{name} blocked e-mail domain %{target}"
+ create_ip_block: "%{name} created rule for IP %{target}"
demote_user: "%{name} demoted user %{target}"
destroy_announcement: "%{name} deleted announcement %{target}"
destroy_custom_emoji: "%{name} destroyed emoji %{target}"
destroy_domain_allow: "%{name} disallowed federation with domain %{target}"
destroy_domain_block: "%{name} unblocked domain %{target}"
destroy_email_domain_block: "%{name} unblocked e-mail domain %{target}"
+ destroy_ip_block: "%{name} deleted rule for IP %{target}"
destroy_status: "%{name} removed status by %{target}"
disable_2fa_user: "%{name} disabled two factor requirement for user %{target}"
disable_custom_emoji: "%{name} disabled emoji %{target}"
@@ -449,6 +453,21 @@ en:
expired: Expired
title: Filter
title: Invites
+ ip_blocks:
+ add_new: Create rule
+ created_msg: Successfully added new IP rule
+ delete: Delete
+ expires_in:
+ '1209600': 2 weeks
+ '15778476': 6 months
+ '2629746': 1 month
+ '31556952': 1 year
+ '86400': 1 day
+ '94670856': 3 years
+ new:
+ title: Create new IP rule
+ no_ip_block_selected: No IP rules were changed as none were selected
+ title: IP rules
pending_accounts:
title: Pending accounts (%{count})
relationships:
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index 910e77ec2a..b694879535 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -65,6 +65,14 @@ en:
data: CSV file exported from another Mastodon server
invite_request:
text: This will help us review your application
+ ip_block:
+ comment: Optional. Remember why you added this rule.
+ expires_in: IP addresses are a finite resource, they are sometimes shared and often change hands. For this reason, indefinite IP blocks are not recommended.
+ ip: Enter an IPv4 or IPv6 address. You can block entire ranges using the CIDR syntax. Be careful not to lock yourself out!
+ severities:
+ no_access: Block access to all resources
+ sign_up_requires_approval: New sign-ups will require your approval
+ severity: Choose what will happen with requests from this IP
sessions:
otp: 'Enter the two-factor code generated by your phone app or use one of your recovery codes:'
webauthn: If it's an USB key be sure to insert it and, if necessary, tap it.
@@ -136,6 +144,7 @@ en:
setting_default_privacy: Posting privacy
setting_default_sensitive: Always mark media as sensitive
setting_delete_modal: Show confirmation dialog before deleting a toot
+ setting_disable_swiping: Disable swiping motions
setting_display_media: Media display
setting_display_media_default: Default
setting_display_media_hide_all: Hide all
@@ -169,6 +178,13 @@ en:
comment: Comment
invite_request:
text: Why do you want to join?
+ ip_block:
+ comment: Comment
+ ip: IP
+ severities:
+ no_access: Block access
+ sign_up_requires_approval: Limit sign-ups
+ severity: Rule
notification_emails:
digest: Send digest e-mails
favourite: Someone favourited your status
diff --git a/config/navigation.rb b/config/navigation.rb
index 6ea267ab0e..be429cfc4f 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -47,6 +47,7 @@ SimpleNavigation::Configuration.run do |navigation|
s.item :tags, safe_join([fa_icon('hashtag fw'), t('admin.tags.title')]), admin_tags_path, highlights_on: %r{/admin/tags}
s.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url(limited: whitelist_mode? ? nil : '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks|/admin/domain_allows}, if: -> { current_user.admin? }
s.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.admin? }
+ s.item :ip_blocks, safe_join([fa_icon('ban fw'), t('admin.ip_blocks.title')]), admin_ip_blocks_url, highlights_on: %r{/admin/ip_blocks}, if: -> { current_user.admin? }
end
n.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_dashboard_url, if: proc { current_user.staff? } do |s|
diff --git a/config/routes.rb b/config/routes.rb
index 25c9b93eca..327dcc58ca 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -83,6 +83,7 @@ Rails.application.routes.draw do
resource :inbox, only: [:create], module: :activitypub
resource :claim, only: [:create], module: :activitypub
resources :collections, only: [:show], module: :activitypub
+ resource :followers_synchronization, only: [:show], module: :activitypub
end
resource :inbox, only: [:create], module: :activitypub
@@ -285,6 +286,12 @@ Rails.application.routes.draw do
end
end
+ resources :ip_blocks, only: [:index, :new, :create] do
+ collection do
+ post :batch
+ end
+ end
+
resources :account_moderation_notes, only: [:create, :destroy]
resources :tags, only: [:index, :show, :update] do
@@ -381,11 +388,7 @@ Rails.application.routes.draw do
resources :media, only: [:create, :update, :show]
resources :blocks, only: [:index]
- resources :mutes, only: [:index] do
- collection do
- get 'details'
- end
- end
+ resources :mutes, only: [:index]
resources :favourites, only: [:index]
resources :bookmarks, only: [:index]
resources :reports, only: [:create]
diff --git a/config/settings.yml b/config/settings.yml
index c61454e9eb..4d6a1cffca 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -27,6 +27,7 @@ defaults: &defaults
expand_spoilers: false
preview_sensitive_media: false
reduce_motion: false
+ disable_swiping: false
show_application: false
system_font_ui: false
system_emoji_font: false
diff --git a/config/webpack/configuration.js b/config/webpack/configuration.js
index 926af9b397..b34ba0e0a6 100644
--- a/config/webpack/configuration.js
+++ b/config/webpack/configuration.js
@@ -57,22 +57,9 @@ for (let i = 0; i < skinFiles.length; i++) {
}
}
-function removeOuterSlashes(string) {
- return string.replace(/^\/*/, '').replace(/\/*$/, '');
-}
-
-function formatPublicPath(host = '', path = '') {
- let formattedHost = removeOuterSlashes(host);
- if (formattedHost && !/^http/i.test(formattedHost)) {
- formattedHost = `//${formattedHost}`;
- }
- const formattedPath = removeOuterSlashes(path);
- return `${formattedHost}/${formattedPath}/`;
-}
-
const output = {
path: resolve('public', settings.public_output_path),
- publicPath: formatPublicPath(env.CDN_HOST, settings.public_output_path),
+ publicPath: `/${settings.public_output_path}/`,
};
module.exports = {
@@ -80,8 +67,8 @@ module.exports = {
core,
flavours,
env: {
- CDN_HOST: env.CDN_HOST,
NODE_ENV: env.NODE_ENV,
+ PUBLIC_OUTPUT_PATH: settings.public_output_path,
},
output,
};
diff --git a/db/migrate/20200317021758_add_expires_at_to_mutes.rb b/db/migrate/20200317021758_add_expires_at_to_mutes.rb
new file mode 100644
index 0000000000..eaae8319d7
--- /dev/null
+++ b/db/migrate/20200317021758_add_expires_at_to_mutes.rb
@@ -0,0 +1,5 @@
+class AddExpiresAtToMutes < ActiveRecord::Migration[5.2]
+ def change
+ add_column :mutes, :expires_at, :datetime
+ end
+end
diff --git a/db/migrate/20201008202037_create_ip_blocks.rb b/db/migrate/20201008202037_create_ip_blocks.rb
new file mode 100644
index 0000000000..32acd6ede1
--- /dev/null
+++ b/db/migrate/20201008202037_create_ip_blocks.rb
@@ -0,0 +1,12 @@
+class CreateIpBlocks < ActiveRecord::Migration[5.2]
+ def change
+ create_table :ip_blocks do |t|
+ t.inet :ip, null: false, default: '0.0.0.0'
+ t.integer :severity, null: false, default: 0
+ t.datetime :expires_at
+ t.text :comment, null: false, default: ''
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20201008220312_add_sign_up_ip_to_users.rb b/db/migrate/20201008220312_add_sign_up_ip_to_users.rb
new file mode 100644
index 0000000000..66cd624bbb
--- /dev/null
+++ b/db/migrate/20201008220312_add_sign_up_ip_to_users.rb
@@ -0,0 +1,5 @@
+class AddSignUpIpToUsers < ActiveRecord::Migration[5.2]
+ def change
+ add_column :users, :sign_up_ip, :inet
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 9678b9d797..6d6f97f0a6 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2020_09_17_222734) do
+ActiveRecord::Schema.define(version: 2020_10_08_220312) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -463,6 +463,15 @@ ActiveRecord::Schema.define(version: 2020_09_17_222734) do
t.index ["user_id"], name: "index_invites_on_user_id"
end
+ create_table "ip_blocks", force: :cascade do |t|
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.datetime "expires_at"
+ t.inet "ip", default: "0.0.0.0", null: false
+ t.integer "severity", default: 0, null: false
+ t.text "comment", default: "", null: false
+ end
+
create_table "list_accounts", force: :cascade do |t|
t.bigint "list_id", null: false
t.bigint "account_id", null: false
@@ -536,6 +545,7 @@ ActiveRecord::Schema.define(version: 2020_09_17_222734) do
t.boolean "hide_notifications", default: true, null: false
t.bigint "account_id", null: false
t.bigint "target_account_id", null: false
+ t.datetime "expires_at"
t.index ["account_id", "target_account_id"], name: "index_mutes_on_account_id_and_target_account_id", unique: true
t.index ["target_account_id"], name: "index_mutes_on_target_account_id"
end
@@ -893,6 +903,7 @@ ActiveRecord::Schema.define(version: 2020_09_17_222734) do
t.string "sign_in_token"
t.datetime "sign_in_token_sent_at"
t.string "webauthn_id"
+ t.inet "sign_up_ip"
t.index ["account_id"], name: "index_users_on_account_id"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["created_by_application_id"], name: "index_users_on_created_by_application_id"
diff --git a/lib/cli.rb b/lib/cli.rb
index 9162144cc4..2a4dd11b21 100644
--- a/lib/cli.rb
+++ b/lib/cli.rb
@@ -13,6 +13,7 @@ require_relative 'mastodon/preview_cards_cli'
require_relative 'mastodon/cache_cli'
require_relative 'mastodon/upgrade_cli'
require_relative 'mastodon/email_domain_blocks_cli'
+require_relative 'mastodon/ip_blocks_cli'
require_relative 'mastodon/version'
module Mastodon
@@ -57,6 +58,9 @@ module Mastodon
desc 'email_domain_blocks SUBCOMMAND ...ARGS', 'Manage e-mail domain blocks'
subcommand 'email_domain_blocks', Mastodon::EmailDomainBlocksCLI
+ desc 'ip_blocks SUBCOMMAND ...ARGS', 'Manage IP blocks'
+ subcommand 'ip_blocks', Mastodon::IpBlocksCLI
+
option :dry_run, type: :boolean
desc 'self-destruct', 'Erase the server from the federation'
long_desc <<~LONG_DESC
diff --git a/lib/mastodon/ip_blocks_cli.rb b/lib/mastodon/ip_blocks_cli.rb
new file mode 100644
index 0000000000..6aff36d908
--- /dev/null
+++ b/lib/mastodon/ip_blocks_cli.rb
@@ -0,0 +1,132 @@
+# frozen_string_literal: true
+
+require 'rubygems/package'
+require_relative '../../config/boot'
+require_relative '../../config/environment'
+require_relative 'cli_helper'
+
+module Mastodon
+ class IpBlocksCLI < Thor
+ def self.exit_on_failure?
+ true
+ end
+
+ option :severity, required: true, enum: %w(no_access sign_up_requires_approval), desc: 'Severity of the block'
+ option :comment, aliases: [:c], desc: 'Optional comment'
+ option :duration, aliases: [:d], type: :numeric, desc: 'Duration of the block in seconds'
+ option :force, type: :boolean, aliases: [:f], desc: 'Overwrite existing blocks'
+ desc 'add IP...', 'Add one or more IP blocks'
+ long_desc <<-LONG_DESC
+ Add one or more IP blocks. You can use CIDR syntax to
+ block IP ranges. You must specify --severity of the block. All
+ options will be copied for each IP block you create in one command.
+
+ You can add a --comment. If an IP block already exists for one of
+ the provided IPs, it will be skipped unless you use the --force
+ option to overwrite it.
+ LONG_DESC
+ def add(*addresses)
+ if addresses.empty?
+ say('No IP(s) given', :red)
+ exit(1)
+ end
+
+ skipped = 0
+ processed = 0
+ failed = 0
+
+ addresses.each do |address|
+ ip_block = IpBlock.find_by(ip: address)
+
+ if ip_block.present? && !options[:force]
+ say("#{address} is already blocked", :yellow)
+ skipped += 1
+ next
+ end
+
+ ip_block ||= IpBlock.new(ip: address)
+
+ ip_block.severity = options[:severity]
+ ip_block.comment = options[:comment]
+ ip_block.expires_in = options[:duration]
+
+ if ip_block.save
+ processed += 1
+ else
+ say("#{address} could not be saved", :red)
+ failed += 1
+ end
+ end
+
+ say("Added #{processed}, skipped #{skipped}, failed #{failed}", color(processed, failed))
+ end
+
+ option :force, type: :boolean, aliases: [:f], desc: 'Remove blocks for ranges that cover given IP(s)'
+ desc 'remove IP...', 'Remove one or more IP blocks'
+ long_desc <<-LONG_DESC
+ Remove one or more IP blocks. Normally, only exact matches are removed. If
+ you want to ensure that all of the given IP addresses are unblocked, you
+ can use --force which will also remove any blocks for IP ranges that would
+ cover the given IP(s).
+ LONG_DESC
+ def remove(*addresses)
+ if addresses.empty?
+ say('No IP(s) given', :red)
+ exit(1)
+ end
+
+ processed = 0
+ skipped = 0
+
+ addresses.each do |address|
+ ip_blocks = begin
+ if options[:force]
+ IpBlock.where('ip >>= ?', address)
+ else
+ IpBlock.where('ip <<= ?', address)
+ end
+ end
+
+ if ip_blocks.empty?
+ say("#{address} is not yet blocked", :yellow)
+ skipped += 1
+ next
+ end
+
+ ip_blocks.in_batches.destroy_all
+ processed += 1
+ end
+
+ say("Removed #{processed}, skipped #{skipped}", color(processed, 0))
+ end
+
+ option :format, aliases: [:f], enum: %w(plain nginx), desc: 'Format of the output'
+ desc 'export', 'Export blocked IPs'
+ long_desc <<-LONG_DESC
+ Export blocked IPs. Different formats are supported for usage with other
+ tools. Only blocks with no_access severity are returned.
+ LONG_DESC
+ def export
+ IpBlock.where(severity: :no_access).find_each do |ip_block|
+ case options[:format]
+ when 'nginx'
+ puts "deny #{ip_block.ip}/#{ip_block.ip.prefix};"
+ else
+ puts "#{ip_block.ip}/#{ip_block.ip.prefix}"
+ end
+ end
+ end
+
+ private
+
+ def color(processed, failed)
+ if !processed.zero? && failed.zero?
+ :green
+ elsif failed.zero?
+ :yellow
+ else
+ :red
+ end
+ end
+ end
+end
diff --git a/package.json b/package.json
index 30dfbc4712..5d07f31a5d 100644
--- a/package.json
+++ b/package.json
@@ -70,7 +70,7 @@
"@babel/runtime": "^7.11.2",
"@clusterws/cws": "^3.0.0",
"@gamestdio/websocket": "^0.3.2",
- "@github/webauthn-json": "^0.5.4",
+ "@github/webauthn-json": "^0.5.6",
"@rails/ujs": "^6.0.3",
"array-includes": "^3.1.1",
"atrament": "0.2.4",
@@ -85,7 +85,7 @@
"babel-runtime": "^6.26.0",
"blurhash": "^1.1.3",
"classnames": "^2.2.5",
- "compression-webpack-plugin": "^5.0.1",
+ "compression-webpack-plugin": "^6.0.3",
"cross-env": "^7.0.2",
"css-loader": "^4.3.0",
"cssnano": "^4.1.10",
@@ -97,13 +97,13 @@
"exif-js": "^2.3.0",
"express": "^4.17.1",
"favico.js": "^0.3.10",
- "file-loader": "^6.1.0",
+ "file-loader": "^6.1.1",
"font-awesome": "^4.7.0",
"glob": "^7.1.6",
"history": "^4.10.1",
- "http-link-header": "^1.0.2",
+ "http-link-header": "^1.0.3",
"immutable": "^3.8.2",
- "imports-loader": "^1.1.0",
+ "imports-loader": "^1.2.0",
"intersection-observer": "^0.11.0",
"intl": "^1.2.5",
"intl-messageformat": "^2.2.0",
@@ -113,7 +113,7 @@
"lodash": "^4.17.19",
"mark-loader": "^0.1.6",
"marky": "^1.2.1",
- "mini-css-extract-plugin": "^0.11.0",
+ "mini-css-extract-plugin": "^0.11.3",
"mkdirp": "^1.0.4",
"npmlog": "^4.1.2",
"object-assign": "^4.1.1",
@@ -136,7 +136,7 @@
"react-masonry-infinite": "^1.2.2",
"react-motion": "^0.5.2",
"react-notification": "^6.8.5",
- "react-overlays": "^0.9.1",
+ "react-overlays": "^0.9.2",
"react-redux": "^7.2.1",
"react-redux-loading-bar": "^4.0.8",
"react-router-dom": "^4.1.1",
@@ -155,34 +155,34 @@
"requestidlecallback": "^0.3.0",
"reselect": "^4.0.0",
"rimraf": "^3.0.2",
- "sass": "^1.26.10",
- "sass-loader": "^10.0.2",
+ "sass": "^1.27.0",
+ "sass-loader": "^10.0.3",
"stacktrace-js": "^2.0.2",
"stringz": "^2.1.0",
"substring-trie": "^1.0.2",
- "terser-webpack-plugin": "^4.2.2",
+ "terser-webpack-plugin": "^4.2.3",
"tesseract.js": "^2.1.1",
"throng": "^4.0.0",
"tiny-queue": "^0.2.1",
- "uuid": "^8.2.0",
+ "uuid": "^8.3.1",
"webpack": "^4.44.2",
"webpack-assets-manifest": "^3.1.1",
- "webpack-bundle-analyzer": "^3.8.0",
+ "webpack-bundle-analyzer": "^3.9.0",
"webpack-cli": "^3.3.12",
"webpack-merge": "^4.2.1",
"wicg-inert": "^3.0.3"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.11.4",
- "@testing-library/react": "^10.4.7",
+ "@testing-library/react": "^11.0.4",
"babel-eslint": "^10.1.0",
- "babel-jest": "^26.3.0",
- "eslint": "^7.6.0",
- "eslint-plugin-import": "~2.22.0",
+ "babel-jest": "^26.5.2",
+ "eslint": "^7.11.0",
+ "eslint-plugin-import": "~2.22.1",
"eslint-plugin-jsx-a11y": "~6.3.1",
"eslint-plugin-promise": "~4.2.1",
- "eslint-plugin-react": "~7.20.4",
- "jest": "^26.4.2",
+ "eslint-plugin-react": "~7.21.4",
+ "jest": "^26.5.3",
"raf": "^3.4.1",
"react-intl-translations-manager": "^5.0.3",
"react-test-renderer": "^16.13.1",
diff --git a/spec/controllers/activitypub/followers_synchronizations_controller_spec.rb b/spec/controllers/activitypub/followers_synchronizations_controller_spec.rb
new file mode 100644
index 0000000000..a24d3f8e08
--- /dev/null
+++ b/spec/controllers/activitypub/followers_synchronizations_controller_spec.rb
@@ -0,0 +1,58 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::FollowersSynchronizationsController, type: :controller do
+ let!(:account) { Fabricate(:account) }
+ let!(:follower_1) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/users/a') }
+ let!(:follower_2) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/users/b') }
+ let!(:follower_3) { Fabricate(:account, domain: 'foo.com', uri: 'https://foo.com/users/a') }
+
+ before do
+ follower_1.follow!(account)
+ follower_2.follow!(account)
+ follower_3.follow!(account)
+ end
+
+ before do
+ allow(controller).to receive(:signed_request_account).and_return(remote_account)
+ end
+
+ describe 'GET #show' do
+ context 'without signature' do
+ let(:remote_account) { nil }
+
+ before do
+ get :show, params: { account_username: account.username }
+ end
+
+ it 'returns http not authorized' do
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'with signature from example.com' do
+ let(:remote_account) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/instance') }
+
+ before do
+ get :show, params: { account_username: account.username }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns application/activity+json' do
+ expect(response.content_type).to eq 'application/activity+json'
+ end
+
+ it 'returns orderedItems with followers from example.com' do
+ json = body_as_json
+ expect(json[:orderedItems]).to be_an Array
+ expect(json[:orderedItems].sort).to eq [follower_1.uri, follower_2.uri]
+ end
+
+ it 'returns private Cache-Control header' do
+ expect(response.headers['Cache-Control']).to eq 'max-age=0, private'
+ end
+ end
+ end
+end
diff --git a/spec/controllers/activitypub/inboxes_controller_spec.rb b/spec/controllers/activitypub/inboxes_controller_spec.rb
index f3bc23953a..e5c0046119 100644
--- a/spec/controllers/activitypub/inboxes_controller_spec.rb
+++ b/spec/controllers/activitypub/inboxes_controller_spec.rb
@@ -22,6 +22,56 @@ RSpec.describe ActivityPub::InboxesController, type: :controller do
end
end
+ context 'with Collection-Synchronization header' do
+ let(:remote_account) { Fabricate(:account, followers_url: 'https://example.com/followers', domain: 'example.com', uri: 'https://example.com/actor', protocol: :activitypub) }
+ let(:synchronization_collection) { remote_account.followers_url }
+ let(:synchronization_url) { 'https://example.com/followers-for-domain' }
+ let(:synchronization_hash) { 'somehash' }
+ let(:synchronization_header) { "collectionId=\"#{synchronization_collection}\", digest=\"#{synchronization_hash}\", url=\"#{synchronization_url}\"" }
+
+ before do
+ allow(ActivityPub::FollowersSynchronizationWorker).to receive(:perform_async).and_return(nil)
+ allow_any_instance_of(Account).to receive(:local_followers_hash).and_return('somehash')
+
+ request.headers['Collection-Synchronization'] = synchronization_header
+ post :create, body: '{}'
+ end
+
+ context 'with mismatching target collection' do
+ let(:synchronization_collection) { 'https://example.com/followers2' }
+
+ it 'does not start a synchronization job' do
+ expect(ActivityPub::FollowersSynchronizationWorker).not_to have_received(:perform_async)
+ end
+ end
+
+ context 'with mismatching domain in partial collection attribute' do
+ let(:synchronization_url) { 'https://example.org/followers' }
+
+ it 'does not start a synchronization job' do
+ expect(ActivityPub::FollowersSynchronizationWorker).not_to have_received(:perform_async)
+ end
+ end
+
+ context 'with matching digest' do
+ it 'does not start a synchronization job' do
+ expect(ActivityPub::FollowersSynchronizationWorker).not_to have_received(:perform_async)
+ end
+ end
+
+ context 'with mismatching digest' do
+ let(:synchronization_hash) { 'wronghash' }
+
+ it 'starts a synchronization job' do
+ expect(ActivityPub::FollowersSynchronizationWorker).to have_received(:perform_async)
+ end
+ end
+
+ it 'returns http accepted' do
+ expect(response).to have_http_status(202)
+ end
+ end
+
context 'without signature' do
before do
post :create, body: '{}'
diff --git a/spec/controllers/api/v1/mutes_controller_spec.rb b/spec/controllers/api/v1/mutes_controller_spec.rb
index 95ec17d6f8..a2b814a690 100644
--- a/spec/controllers/api/v1/mutes_controller_spec.rb
+++ b/spec/controllers/api/v1/mutes_controller_spec.rb
@@ -60,25 +60,4 @@ RSpec.describe Api::V1::MutesController, type: :controller do
end
end
end
-
- describe 'GET #details' do
- before do
- Fabricate(:mute, account: user.account, hide_notifications: false)
- get :details, params: { limit: 1 }
- end
-
- let(:mutes) { JSON.parse(response.body) }
-
- it 'returns http success' do
- expect(response).to have_http_status(:success)
- end
-
- it 'returns one mute' do
- expect(mutes.size).to be(1)
- end
-
- it 'returns whether the mute hides notifications' do
- expect(mutes.first["hide_notifications"]).to be(false)
- end
- end
end
diff --git a/spec/controllers/remote_follow_controller_spec.rb b/spec/controllers/remote_follow_controller_spec.rb
index 3ef8f14d9f..7312dde582 100644
--- a/spec/controllers/remote_follow_controller_spec.rb
+++ b/spec/controllers/remote_follow_controller_spec.rb
@@ -43,8 +43,7 @@ describe RemoteFollowController do
end
it 'renders new when template is nil' do
- link_with_nil_template = double(template: nil)
- resource_with_link = double(link: link_with_nil_template)
+ resource_with_link = double(link: nil)
allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_link)
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
@@ -55,8 +54,7 @@ describe RemoteFollowController do
context 'when webfinger values are good' do
before do
- link_with_template = double(template: 'http://example.com/follow_me?acct={uri}')
- resource_with_link = double(link: link_with_template)
+ resource_with_link = double(link: 'http://example.com/follow_me?acct={uri}')
allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_link)
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
end
@@ -78,8 +76,8 @@ describe RemoteFollowController do
expect(response).to render_template(:new)
end
- it 'renders new with error when goldfinger fails' do
- allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_raise(Goldfinger::Error)
+ it 'renders new with error when webfinger fails' do
+ allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_raise(Webfinger::Error)
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
expect(response).to render_template(:new)
diff --git a/spec/controllers/well_known/host_meta_controller_spec.rb b/spec/controllers/well_known/host_meta_controller_spec.rb
index b43ae19d87..643ba9cd32 100644
--- a/spec/controllers/well_known/host_meta_controller_spec.rb
+++ b/spec/controllers/well_known/host_meta_controller_spec.rb
@@ -12,7 +12,7 @@ describe WellKnown::HostMetaController, type: :controller do
expect(response.body).to eq <
-
+
XML
end
diff --git a/spec/fabricators/ip_block_fabricator.rb b/spec/fabricators/ip_block_fabricator.rb
new file mode 100644
index 0000000000..31dc336e64
--- /dev/null
+++ b/spec/fabricators/ip_block_fabricator.rb
@@ -0,0 +1,6 @@
+Fabricator(:ip_block) do
+ ip ""
+ severity ""
+ expires_at "2020-10-08 22:20:37"
+ comment "MyText"
+end
\ No newline at end of file
diff --git a/spec/lib/fast_ip_map_spec.rb b/spec/lib/fast_ip_map_spec.rb
new file mode 100644
index 0000000000..c66f64828a
--- /dev/null
+++ b/spec/lib/fast_ip_map_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe FastIpMap do
+ describe '#include?' do
+ subject { described_class.new([IPAddr.new('20.4.0.0/16'), IPAddr.new('145.22.30.0/24'), IPAddr.new('189.45.86.3')])}
+
+ it 'returns true for an exact match' do
+ expect(subject.include?(IPAddr.new('189.45.86.3'))).to be true
+ end
+
+ it 'returns true for a range match' do
+ expect(subject.include?(IPAddr.new('20.4.45.7'))).to be true
+ end
+
+ it 'returns false for no match' do
+ expect(subject.include?(IPAddr.new('145.22.40.64'))).to be false
+ end
+ end
+end
diff --git a/spec/lib/feed_manager_spec.rb b/spec/lib/feed_manager_spec.rb
index 22c9ff31b4..78563ee94b 100644
--- a/spec/lib/feed_manager_spec.rb
+++ b/spec/lib/feed_manager_spec.rb
@@ -108,6 +108,7 @@ RSpec.describe FeedManager do
it 'returns false for status by followee mentioning another account' do
bob.follow!(alice)
+ jeff.follow!(alice)
status = PostStatusService.new.call(alice, text: 'Hey @jeff')
expect(FeedManager.instance.filter?(:home, status, bob)).to be false
end
diff --git a/spec/models/concerns/account_interactions_spec.rb b/spec/models/concerns/account_interactions_spec.rb
index 48b44e58df..db959280c6 100644
--- a/spec/models/concerns/account_interactions_spec.rb
+++ b/spec/models/concerns/account_interactions_spec.rb
@@ -546,6 +546,49 @@ describe AccountInteractions do
end
end
+ describe '#followers_hash' do
+ let(:me) { Fabricate(:account, username: 'Me') }
+ let(:remote_1) { Fabricate(:account, username: 'alice', domain: 'example.org', uri: 'https://example.org/users/alice') }
+ let(:remote_2) { Fabricate(:account, username: 'bob', domain: 'example.org', uri: 'https://example.org/users/bob') }
+ let(:remote_3) { Fabricate(:account, username: 'eve', domain: 'foo.org', uri: 'https://foo.org/users/eve') }
+
+ before do
+ remote_1.follow!(me)
+ remote_2.follow!(me)
+ remote_3.follow!(me)
+ me.follow!(remote_1)
+ end
+
+ context 'on a local user' do
+ it 'returns correct hash for remote domains' do
+ expect(me.remote_followers_hash('https://example.org/')).to eq '707962e297b7bd94468a21bc8e506a1bcea607a9142cd64e27c9b106b2a5f6ec'
+ expect(me.remote_followers_hash('https://foo.org/')).to eq 'ccb9c18a67134cfff9d62c7f7e7eb88e6b803446c244b84265565f4eba29df0e'
+ end
+
+ it 'invalidates cache as needed when removing or adding followers' do
+ expect(me.remote_followers_hash('https://example.org/')).to eq '707962e297b7bd94468a21bc8e506a1bcea607a9142cd64e27c9b106b2a5f6ec'
+ remote_1.unfollow!(me)
+ expect(me.remote_followers_hash('https://example.org/')).to eq '241b00794ce9b46aa864f3220afadef128318da2659782985bac5ed5bd436bff'
+ remote_1.follow!(me)
+ expect(me.remote_followers_hash('https://example.org/')).to eq '707962e297b7bd94468a21bc8e506a1bcea607a9142cd64e27c9b106b2a5f6ec'
+ end
+ end
+
+ context 'on a remote user' do
+ it 'returns correct hash for remote domains' do
+ expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
+ end
+
+ it 'invalidates cache as needed when removing or adding followers' do
+ expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
+ me.unfollow!(remote_1)
+ expect(remote_1.local_followers_hash).to eq '0000000000000000000000000000000000000000000000000000000000000000'
+ me.follow!(remote_1)
+ expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
+ end
+ end
+ end
+
describe 'muting an account' do
let(:me) { Fabricate(:account, username: 'Me') }
let(:you) { Fabricate(:account, username: 'You') }
diff --git a/spec/models/ip_block_spec.rb b/spec/models/ip_block_spec.rb
new file mode 100644
index 0000000000..6603c6417a
--- /dev/null
+++ b/spec/models/ip_block_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe IpBlock, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/services/activitypub/synchronize_followers_service_spec.rb b/spec/services/activitypub/synchronize_followers_service_spec.rb
new file mode 100644
index 0000000000..75dcf204b7
--- /dev/null
+++ b/spec/services/activitypub/synchronize_followers_service_spec.rb
@@ -0,0 +1,105 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::SynchronizeFollowersService, type: :service do
+ let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account', inbox_url: 'http://example.com/inbox') }
+ let(:alice) { Fabricate(:account, username: 'alice') }
+ let(:bob) { Fabricate(:account, username: 'bob') }
+ let(:eve) { Fabricate(:account, username: 'eve') }
+ let(:mallory) { Fabricate(:account, username: 'mallory') }
+ let(:collection_uri) { 'http://example.com/partial-followers' }
+
+ let(:items) do
+ [
+ ActivityPub::TagManager.instance.uri_for(alice),
+ ActivityPub::TagManager.instance.uri_for(eve),
+ ActivityPub::TagManager.instance.uri_for(mallory),
+ ]
+ end
+
+ let(:payload) do
+ {
+ '@context': 'https://www.w3.org/ns/activitystreams',
+ type: 'Collection',
+ id: collection_uri,
+ items: items,
+ }.with_indifferent_access
+ end
+
+ subject { described_class.new }
+
+ shared_examples 'synchronizes followers' do
+ before do
+ alice.follow!(actor)
+ bob.follow!(actor)
+ mallory.request_follow!(actor)
+
+ allow(ActivityPub::DeliveryWorker).to receive(:perform_async)
+
+ subject.call(actor, collection_uri)
+ end
+
+ it 'keeps expected followers' do
+ expect(alice.following?(actor)).to be true
+ end
+
+ it 'removes local followers not in the remote list' do
+ expect(bob.following?(actor)).to be false
+ end
+
+ it 'converts follow requests to follow relationships when they have been accepted' do
+ expect(mallory.following?(actor)).to be true
+ end
+
+ it 'sends an Undo Follow to the actor' do
+ expect(ActivityPub::DeliveryWorker).to have_received(:perform_async).with(anything, eve.id, actor.inbox_url)
+ end
+ end
+
+ describe '#call' do
+ context 'when the endpoint is a Collection of actor URIs' do
+ before do
+ stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload))
+ end
+
+ it_behaves_like 'synchronizes followers'
+ end
+
+ context 'when the endpoint is an OrderedCollection of actor URIs' do
+ let(:payload) do
+ {
+ '@context': 'https://www.w3.org/ns/activitystreams',
+ type: 'OrderedCollection',
+ id: collection_uri,
+ orderedItems: items,
+ }.with_indifferent_access
+ end
+
+ before do
+ stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload))
+ end
+
+ it_behaves_like 'synchronizes followers'
+ end
+
+ context 'when the endpoint is a paginated Collection of actor URIs' do
+ let(:payload) do
+ {
+ '@context': 'https://www.w3.org/ns/activitystreams',
+ type: 'Collection',
+ id: collection_uri,
+ first: {
+ type: 'CollectionPage',
+ partOf: collection_uri,
+ items: items,
+ }
+ }.with_indifferent_access
+ end
+
+ before do
+ stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload))
+ end
+
+ it_behaves_like 'synchronizes followers'
+ end
+ end
+end
diff --git a/spec/services/app_sign_up_service_spec.rb b/spec/services/app_sign_up_service_spec.rb
index e7c7f3ba15..e0c83b7041 100644
--- a/spec/services/app_sign_up_service_spec.rb
+++ b/spec/services/app_sign_up_service_spec.rb
@@ -3,6 +3,7 @@ require 'rails_helper'
RSpec.describe AppSignUpService, type: :service do
let(:app) { Fabricate(:application, scopes: 'read write') }
let(:good_params) { { username: 'alice', password: '12345678', email: 'good@email.com', agreement: true } }
+ let(:remote_ip) { IPAddr.new('198.0.2.1') }
subject { described_class.new }
@@ -10,16 +11,16 @@ RSpec.describe AppSignUpService, type: :service do
it 'returns nil when registrations are closed' do
tmp = Setting.registrations_mode
Setting.registrations_mode = 'none'
- expect(subject.call(app, good_params)).to be_nil
+ expect(subject.call(app, remote_ip, good_params)).to be_nil
Setting.registrations_mode = tmp
end
it 'raises an error when params are missing' do
- expect { subject.call(app, {}) }.to raise_error ActiveRecord::RecordInvalid
+ expect { subject.call(app, remote_ip, {}) }.to raise_error ActiveRecord::RecordInvalid
end
it 'creates an unconfirmed user with access token' do
- access_token = subject.call(app, good_params)
+ access_token = subject.call(app, remote_ip, good_params)
expect(access_token).to_not be_nil
user = User.find_by(id: access_token.resource_owner_id)
expect(user).to_not be_nil
@@ -27,13 +28,13 @@ RSpec.describe AppSignUpService, type: :service do
end
it 'creates access token with the app\'s scopes' do
- access_token = subject.call(app, good_params)
+ access_token = subject.call(app, remote_ip, good_params)
expect(access_token).to_not be_nil
expect(access_token.scopes.to_s).to eq 'read write'
end
it 'creates an account' do
- access_token = subject.call(app, good_params)
+ access_token = subject.call(app, remote_ip, good_params)
expect(access_token).to_not be_nil
user = User.find_by(id: access_token.resource_owner_id)
expect(user).to_not be_nil
@@ -42,7 +43,7 @@ RSpec.describe AppSignUpService, type: :service do
end
it 'creates an account with invite request text' do
- access_token = subject.call(app, good_params.merge(reason: 'Foo bar'))
+ access_token = subject.call(app, remote_ip, good_params.merge(reason: 'Foo bar'))
expect(access_token).to_not be_nil
user = User.find_by(id: access_token.resource_owner_id)
expect(user).to_not be_nil
diff --git a/spec/workers/activitypub/delivery_worker_spec.rb b/spec/workers/activitypub/delivery_worker_spec.rb
index 351be185cd..f4633731e5 100644
--- a/spec/workers/activitypub/delivery_worker_spec.rb
+++ b/spec/workers/activitypub/delivery_worker_spec.rb
@@ -3,16 +3,22 @@
require 'rails_helper'
describe ActivityPub::DeliveryWorker do
+ include RoutingHelper
+
subject { described_class.new }
let(:sender) { Fabricate(:account) }
let(:payload) { 'test' }
+ before do
+ allow_any_instance_of(Account).to receive(:remote_followers_hash).with('https://example.com/').and_return('somehash')
+ end
+
describe 'perform' do
it 'performs a request' do
stub_request(:post, 'https://example.com/api').to_return(status: 200)
- subject.perform(payload, sender.id, 'https://example.com/api')
- expect(a_request(:post, 'https://example.com/api')).to have_been_made.once
+ subject.perform(payload, sender.id, 'https://example.com/api', { synchronize_followers: true })
+ expect(a_request(:post, 'https://example.com/api').with(headers: { 'Collection-Synchronization' => "collectionId=\"#{account_followers_url(sender)}\", digest=\"somehash\", url=\"#{account_followers_synchronization_url(sender)}\"" })).to have_been_made.once
end
it 'raises when request fails' do
diff --git a/yarn.lock b/yarn.lock
index 04a3020ed5..1d1f8ad256 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1095,6 +1095,22 @@
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.4.tgz#622a72bebd1e3f48d921563b4b60a762295a81fc"
integrity sha512-6PYY5DVdAY1ifaQW6XYTnOMihmBVT27elqSjEoodchsGjzYlEsTQMcEhSud99kVawatyTZRTiVkJ/c6lwbQ7nA==
+"@eslint/eslintrc@^0.1.3":
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.1.3.tgz#7d1a2b2358552cc04834c0979bd4275362e37085"
+ integrity sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==
+ dependencies:
+ ajv "^6.12.4"
+ debug "^4.1.1"
+ espree "^7.3.0"
+ globals "^12.1.0"
+ ignore "^4.0.6"
+ import-fresh "^3.2.1"
+ js-yaml "^3.13.1"
+ lodash "^4.17.19"
+ minimatch "^3.0.4"
+ strip-json-comments "^3.1.1"
+
"@formatjs/intl-unified-numberformat@^3.3.3":
version "3.3.6"
resolved "https://registry.yarnpkg.com/@formatjs/intl-unified-numberformat/-/intl-unified-numberformat-3.3.6.tgz#ab69818f7568894023cb31fdb5b5c7eed62c6537"
@@ -1112,10 +1128,10 @@
resolved "https://registry.yarnpkg.com/@gamestdio/websocket/-/websocket-0.3.2.tgz#321ba0976ee30fd14e51dbf8faa85ce7b325f76a"
integrity sha512-J3n5SKim+ZoLbe44hRGI/VYAwSMCeIJuBy+FfP6EZaujEpNchPRFcIsVQLWAwpU1bP2Ji63rC+rEUOd1vjUB6Q==
-"@github/webauthn-json@^0.5.4":
- version "0.5.4"
- resolved "https://registry.yarnpkg.com/@github/webauthn-json/-/webauthn-json-0.5.4.tgz#fe4f47647c2b29e42e33f6f30484fec7e5adfeb1"
- integrity sha512-lZi5cSZi2F08a2kmjxr/FU13ILenpxkZJUWo1p3hHAmMHLLr5GAVlkCJmvWeIsIiAp65AHh3dQIZSMPIylcClw==
+"@github/webauthn-json@^0.5.6":
+ version "0.5.6"
+ resolved "https://registry.yarnpkg.com/@github/webauthn-json/-/webauthn-json-0.5.6.tgz#dd0c2c284d6b1b806fea3c534da72d65cbdafed2"
+ integrity sha512-38PqUby4jxRHFKryRFsqmlznXrl/Tray0lmHOaLq12uTdLIjO4i++FUrThf/IOcNmee+qYhEZS1kVSOuSPbY2Q==
"@istanbuljs/load-nyc-config@^1.0.0":
version "1.1.0"
@@ -1133,93 +1149,93 @@
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd"
integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==
-"@jest/console@^26.3.0":
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.3.0.tgz#ed04063efb280c88ba87388b6f16427c0a85c856"
- integrity sha512-/5Pn6sJev0nPUcAdpJHMVIsA8sKizL2ZkcKPE5+dJrCccks7tcM7c9wbgHudBJbxXLoTbqsHkG1Dofoem4F09w==
+"@jest/console@^26.5.2":
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.5.2.tgz#94fc4865b1abed7c352b5e21e6c57be4b95604a6"
+ integrity sha512-lJELzKINpF1v74DXHbCRIkQ/+nUV1M+ntj+X1J8LxCgpmJZjfLmhFejiMSbjjD66fayxl5Z06tbs3HMyuik6rw==
dependencies:
- "@jest/types" "^26.3.0"
+ "@jest/types" "^26.5.2"
"@types/node" "*"
chalk "^4.0.0"
- jest-message-util "^26.3.0"
- jest-util "^26.3.0"
+ jest-message-util "^26.5.2"
+ jest-util "^26.5.2"
slash "^3.0.0"
-"@jest/core@^26.4.2":
- version "26.4.2"
- resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.4.2.tgz#85d0894f31ac29b5bab07aa86806d03dd3d33edc"
- integrity sha512-sDva7YkeNprxJfepOctzS8cAk9TOekldh+5FhVuXS40+94SHbiicRO1VV2tSoRtgIo+POs/Cdyf8p76vPTd6dg==
+"@jest/core@^26.5.3":
+ version "26.5.3"
+ resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.5.3.tgz#712ed4adb64c3bda256a3f400ff1d3eb2a031f13"
+ integrity sha512-CiU0UKFF1V7KzYTVEtFbFmGLdb2g4aTtY0WlyUfLgj/RtoTnJFhh50xKKr7OYkdmBUlGFSa2mD1TU3UZ6OLd4g==
dependencies:
- "@jest/console" "^26.3.0"
- "@jest/reporters" "^26.4.1"
- "@jest/test-result" "^26.3.0"
- "@jest/transform" "^26.3.0"
- "@jest/types" "^26.3.0"
+ "@jest/console" "^26.5.2"
+ "@jest/reporters" "^26.5.3"
+ "@jest/test-result" "^26.5.2"
+ "@jest/transform" "^26.5.2"
+ "@jest/types" "^26.5.2"
"@types/node" "*"
ansi-escapes "^4.2.1"
chalk "^4.0.0"
exit "^0.1.2"
graceful-fs "^4.2.4"
- jest-changed-files "^26.3.0"
- jest-config "^26.4.2"
- jest-haste-map "^26.3.0"
- jest-message-util "^26.3.0"
+ jest-changed-files "^26.5.2"
+ jest-config "^26.5.3"
+ jest-haste-map "^26.5.2"
+ jest-message-util "^26.5.2"
jest-regex-util "^26.0.0"
- jest-resolve "^26.4.0"
- jest-resolve-dependencies "^26.4.2"
- jest-runner "^26.4.2"
- jest-runtime "^26.4.2"
- jest-snapshot "^26.4.2"
- jest-util "^26.3.0"
- jest-validate "^26.4.2"
- jest-watcher "^26.3.0"
+ jest-resolve "^26.5.2"
+ jest-resolve-dependencies "^26.5.3"
+ jest-runner "^26.5.3"
+ jest-runtime "^26.5.3"
+ jest-snapshot "^26.5.3"
+ jest-util "^26.5.2"
+ jest-validate "^26.5.3"
+ jest-watcher "^26.5.2"
micromatch "^4.0.2"
p-each-series "^2.1.0"
rimraf "^3.0.0"
slash "^3.0.0"
strip-ansi "^6.0.0"
-"@jest/environment@^26.3.0":
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.3.0.tgz#e6953ab711ae3e44754a025f838bde1a7fd236a0"
- integrity sha512-EW+MFEo0DGHahf83RAaiqQx688qpXgl99wdb8Fy67ybyzHwR1a58LHcO376xQJHfmoXTu89M09dH3J509cx2AA==
+"@jest/environment@^26.5.2":
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.5.2.tgz#eba3cfc698f6e03739628f699c28e8a07f5e65fe"
+ integrity sha512-YjhCD/Zhkz0/1vdlS/QN6QmuUdDkpgBdK4SdiVg4Y19e29g4VQYN5Xg8+YuHjdoWGY7wJHMxc79uDTeTOy9Ngw==
dependencies:
- "@jest/fake-timers" "^26.3.0"
- "@jest/types" "^26.3.0"
+ "@jest/fake-timers" "^26.5.2"
+ "@jest/types" "^26.5.2"
"@types/node" "*"
- jest-mock "^26.3.0"
+ jest-mock "^26.5.2"
-"@jest/fake-timers@^26.3.0":
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.3.0.tgz#f515d4667a6770f60ae06ae050f4e001126c666a"
- integrity sha512-ZL9ytUiRwVP8ujfRepffokBvD2KbxbqMhrXSBhSdAhISCw3gOkuntisiSFv+A6HN0n0fF4cxzICEKZENLmW+1A==
+"@jest/fake-timers@^26.5.2":
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.5.2.tgz#1291ac81680ceb0dc7daa1f92c059307eea6400a"
+ integrity sha512-09Hn5Oraqt36V1akxQeWMVL0fR9c6PnEhpgLaYvREXZJAh2H2Y+QLCsl0g7uMoJeoWJAuz4tozk1prbR1Fc1sw==
dependencies:
- "@jest/types" "^26.3.0"
+ "@jest/types" "^26.5.2"
"@sinonjs/fake-timers" "^6.0.1"
"@types/node" "*"
- jest-message-util "^26.3.0"
- jest-mock "^26.3.0"
- jest-util "^26.3.0"
+ jest-message-util "^26.5.2"
+ jest-mock "^26.5.2"
+ jest-util "^26.5.2"
-"@jest/globals@^26.4.2":
- version "26.4.2"
- resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.4.2.tgz#73c2a862ac691d998889a241beb3dc9cada40d4a"
- integrity sha512-Ot5ouAlehhHLRhc+sDz2/9bmNv9p5ZWZ9LE1pXGGTCXBasmi5jnYjlgYcYt03FBwLmZXCZ7GrL29c33/XRQiow==
+"@jest/globals@^26.5.3":
+ version "26.5.3"
+ resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.5.3.tgz#90769b40e0af3fa0b28f6d8c5bbe3712467243fd"
+ integrity sha512-7QztI0JC2CuB+Wx1VdnOUNeIGm8+PIaqngYsZXQCkH2QV0GFqzAYc9BZfU0nuqA6cbYrWh5wkuMzyii3P7deug==
dependencies:
- "@jest/environment" "^26.3.0"
- "@jest/types" "^26.3.0"
- expect "^26.4.2"
+ "@jest/environment" "^26.5.2"
+ "@jest/types" "^26.5.2"
+ expect "^26.5.3"
-"@jest/reporters@^26.4.1":
- version "26.4.1"
- resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.4.1.tgz#3b4d6faf28650f3965f8b97bc3d114077fb71795"
- integrity sha512-aROTkCLU8++yiRGVxLsuDmZsQEKO6LprlrxtAuzvtpbIFl3eIjgIf3EUxDKgomkS25R9ZzwGEdB5weCcBZlrpQ==
+"@jest/reporters@^26.5.3":
+ version "26.5.3"
+ resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.5.3.tgz#e810e9c2b670f33f1c09e9975749260ca12f1c17"
+ integrity sha512-X+vR0CpfMQzYcYmMFKNY9n4jklcb14Kffffp7+H/MqitWnb0440bW2L76NGWKAa+bnXhNoZr+lCVtdtPmfJVOQ==
dependencies:
"@bcoe/v8-coverage" "^0.2.3"
- "@jest/console" "^26.3.0"
- "@jest/test-result" "^26.3.0"
- "@jest/transform" "^26.3.0"
- "@jest/types" "^26.3.0"
+ "@jest/console" "^26.5.2"
+ "@jest/test-result" "^26.5.2"
+ "@jest/transform" "^26.5.2"
+ "@jest/types" "^26.5.2"
chalk "^4.0.0"
collect-v8-coverage "^1.0.0"
exit "^0.1.2"
@@ -1230,63 +1246,63 @@
istanbul-lib-report "^3.0.0"
istanbul-lib-source-maps "^4.0.0"
istanbul-reports "^3.0.2"
- jest-haste-map "^26.3.0"
- jest-resolve "^26.4.0"
- jest-util "^26.3.0"
- jest-worker "^26.3.0"
+ jest-haste-map "^26.5.2"
+ jest-resolve "^26.5.2"
+ jest-util "^26.5.2"
+ jest-worker "^26.5.0"
slash "^3.0.0"
source-map "^0.6.0"
string-length "^4.0.1"
terminal-link "^2.0.0"
- v8-to-istanbul "^5.0.1"
+ v8-to-istanbul "^6.0.1"
optionalDependencies:
node-notifier "^8.0.0"
-"@jest/source-map@^26.3.0":
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.3.0.tgz#0e646e519883c14c551f7b5ae4ff5f1bfe4fc3d9"
- integrity sha512-hWX5IHmMDWe1kyrKl7IhFwqOuAreIwHhbe44+XH2ZRHjrKIh0LO5eLQ/vxHFeAfRwJapmxuqlGAEYLadDq6ZGQ==
+"@jest/source-map@^26.5.0":
+ version "26.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.5.0.tgz#98792457c85bdd902365cd2847b58fff05d96367"
+ integrity sha512-jWAw9ZwYHJMe9eZq/WrsHlwF8E3hM9gynlcDpOyCb9bR8wEd9ZNBZCi7/jZyzHxC7t3thZ10gO2IDhu0bPKS5g==
dependencies:
callsites "^3.0.0"
graceful-fs "^4.2.4"
source-map "^0.6.0"
-"@jest/test-result@^26.3.0":
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.3.0.tgz#46cde01fa10c0aaeb7431bf71e4a20d885bc7fdb"
- integrity sha512-a8rbLqzW/q7HWheFVMtghXV79Xk+GWwOK1FrtimpI5n1la2SY0qHri3/b0/1F0Ve0/yJmV8pEhxDfVwiUBGtgg==
+"@jest/test-result@^26.5.2":
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.5.2.tgz#cc1a44cfd4db2ecee3fb0bc4e9fe087aa54b5230"
+ integrity sha512-E/Zp6LURJEGSCWpoMGmCFuuEI1OWuI3hmZwmULV0GsgJBh7u0rwqioxhRU95euUuviqBDN8ruX/vP/4bwYolXw==
dependencies:
- "@jest/console" "^26.3.0"
- "@jest/types" "^26.3.0"
+ "@jest/console" "^26.5.2"
+ "@jest/types" "^26.5.2"
"@types/istanbul-lib-coverage" "^2.0.0"
collect-v8-coverage "^1.0.0"
-"@jest/test-sequencer@^26.4.2":
- version "26.4.2"
- resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.4.2.tgz#58a3760a61eec758a2ce6080201424580d97cbba"
- integrity sha512-83DRD8N3M0tOhz9h0bn6Kl6dSp+US6DazuVF8J9m21WAp5x7CqSMaNycMP0aemC/SH/pDQQddbsfHRTBXVUgog==
+"@jest/test-sequencer@^26.5.3":
+ version "26.5.3"
+ resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.5.3.tgz#9ae0ab9bc37d5171b28424029192e50229814f8d"
+ integrity sha512-Wqzb7aQ13L3T47xHdpUqYMOpiqz6Dx2QDDghp5AV/eUDXR7JieY+E1s233TQlNyl+PqtqgjVokmyjzX/HA51BA==
dependencies:
- "@jest/test-result" "^26.3.0"
+ "@jest/test-result" "^26.5.2"
graceful-fs "^4.2.4"
- jest-haste-map "^26.3.0"
- jest-runner "^26.4.2"
- jest-runtime "^26.4.2"
+ jest-haste-map "^26.5.2"
+ jest-runner "^26.5.3"
+ jest-runtime "^26.5.3"
-"@jest/transform@^26.3.0":
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.3.0.tgz#c393e0e01459da8a8bfc6d2a7c2ece1a13e8ba55"
- integrity sha512-Isj6NB68QorGoFWvcOjlUhpkT56PqNIsXKR7XfvoDlCANn/IANlh8DrKAA2l2JKC3yWSMH5wS0GwuQM20w3b2A==
+"@jest/transform@^26.5.2":
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.5.2.tgz#6a0033a1d24316a1c75184d010d864f2c681bef5"
+ integrity sha512-AUNjvexh+APhhmS8S+KboPz+D3pCxPvEAGduffaAJYxIFxGi/ytZQkrqcKDUU0ERBAo5R7087fyOYr2oms1seg==
dependencies:
"@babel/core" "^7.1.0"
- "@jest/types" "^26.3.0"
+ "@jest/types" "^26.5.2"
babel-plugin-istanbul "^6.0.0"
chalk "^4.0.0"
convert-source-map "^1.4.0"
fast-json-stable-stringify "^2.0.0"
graceful-fs "^4.2.4"
- jest-haste-map "^26.3.0"
+ jest-haste-map "^26.5.2"
jest-regex-util "^26.0.0"
- jest-util "^26.3.0"
+ jest-util "^26.5.2"
micromatch "^4.0.2"
pirates "^4.0.1"
slash "^3.0.0"
@@ -1303,10 +1319,10 @@
"@types/yargs" "^15.0.0"
chalk "^3.0.0"
-"@jest/types@^26.3.0":
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.3.0.tgz#97627bf4bdb72c55346eef98e3b3f7ddc4941f71"
- integrity sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==
+"@jest/types@^26.3.0", "@jest/types@^26.5.2":
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.5.2.tgz#44c24f30c8ee6c7f492ead9ec3f3c62a5289756d"
+ integrity sha512-QDs5d0gYiyetI8q+2xWdkixVQMklReZr4ltw7GFDtb4fuJIBCE6mzj2LnitGqCuAlLap6wPyb8fpoHgwZz5fdg==
dependencies:
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/istanbul-reports" "^3.0.0"
@@ -1340,15 +1356,18 @@
dependencies:
"@sinonjs/commons" "^1.7.0"
-"@testing-library/dom@^7.17.1":
- version "7.18.1"
- resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.18.1.tgz#c49530410fb184522b3b59c4f9cd6397dc5b462d"
- integrity sha512-tGq4KAFjaI7j375sMM1RRVleWA0viJWs/w69B+nyDkqYLNkhdTHdV6mGkspJlkn3PUfyBDi3rERDv4PA/LrpVA==
+"@testing-library/dom@^7.24.2":
+ version "7.24.3"
+ resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.24.3.tgz#dae3071463cf28dc7755b43d9cf2202e34cbb85d"
+ integrity sha512-6eW9fUhEbR423FZvoHRwbWm9RUUByLWGayYFNVvqTnQLYvsNpBS4uEuKH9aqr3trhxFwGVneJUonehL3B1sHJw==
dependencies:
+ "@babel/code-frame" "^7.10.4"
"@babel/runtime" "^7.10.3"
+ "@types/aria-query" "^4.2.0"
aria-query "^4.2.2"
- dom-accessibility-api "^0.4.5"
- pretty-format "^25.5.0"
+ chalk "^4.1.0"
+ dom-accessibility-api "^0.5.1"
+ pretty-format "^26.4.2"
"@testing-library/jest-dom@^5.11.4":
version "5.11.4"
@@ -1364,13 +1383,18 @@
lodash "^4.17.15"
redent "^3.0.0"
-"@testing-library/react@^10.4.7":
- version "10.4.7"
- resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-10.4.7.tgz#fc14847fb70a5e93576b8f7f0d1490ead02a9061"
- integrity sha512-hUYbum3X2f1ZKusKfPaooKNYqE/GtPiQ+D2HJaJ4pkxeNJQFVUEvAvEh9+3QuLdBeTWkDMNY5NSijc5+pGdM4Q==
+"@testing-library/react@^11.0.4":
+ version "11.0.4"
+ resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.0.4.tgz#c84082bfe1593d8fcd475d46baee024452f31dee"
+ integrity sha512-U0fZO2zxm7M0CB5h1+lh31lbAwMSmDMEMGpMT3BUPJwIjDEKYWOV4dx7lb3x2Ue0Pyt77gmz/VropuJnSz/Iew==
dependencies:
- "@babel/runtime" "^7.10.3"
- "@testing-library/dom" "^7.17.1"
+ "@babel/runtime" "^7.11.2"
+ "@testing-library/dom" "^7.24.2"
+
+"@types/aria-query@^4.2.0":
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0"
+ integrity sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.3", "@types/babel__core@^7.1.7":
version "7.1.9"
@@ -1405,6 +1429,13 @@
dependencies:
"@babel/types" "^7.3.0"
+"@types/babel__traverse@^7.0.4":
+ version "7.0.15"
+ resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.15.tgz#db9e4238931eb69ef8aab0ad6523d4d4caa39d03"
+ integrity sha512-Pzh9O3sTK8V6I1olsXpCfj2k/ygO2q1X0vhhnDrEQyYLHZesWz+zMZMVcwXLCYf0U36EtmyYaFGPfXlTtDHe3A==
+ dependencies:
+ "@babel/types" "^7.3.0"
+
"@types/color-name@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
@@ -1466,7 +1497,7 @@
jest-diff "^25.2.1"
pretty-format "^25.2.1"
-"@types/json-schema@^7.0.5":
+"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6":
version "7.0.6"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==
@@ -1511,10 +1542,10 @@
resolved "https://registry.yarnpkg.com/@types/schema-utils/-/schema-utils-1.0.0.tgz#295d36f01e2cb8bc3207ca1d9a68e210db6b40cb"
integrity sha512-YesPanU1+WCigC/Aj1Mga8UCOjHIfMNHZ3zzDsUY7lI8GlKnh/Kv2QwJOQ+jNQ36Ru7IfzSedlG14hppYaN13A==
-"@types/stack-utils@^1.0.1":
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
- integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
+"@types/stack-utils@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff"
+ integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==
"@types/testing-library__jest-dom@^5.9.1":
version "5.9.1"
@@ -1743,11 +1774,16 @@ acorn@^6.4.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474"
integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==
-acorn@^7.1.1, acorn@^7.3.1:
+acorn@^7.1.1:
version "7.4.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c"
integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==
+acorn@^7.4.0:
+ version "7.4.1"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
+ integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
+
aggregate-error@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
@@ -1789,6 +1825,16 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.9.1:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
+ajv@^6.12.5:
+ version "6.12.6"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
+ integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.4.1"
+ uri-js "^4.2.2"
+
alphanum-sort@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
@@ -2107,16 +2153,16 @@ babel-eslint@^10.1.0:
eslint-visitor-keys "^1.0.0"
resolve "^1.12.0"
-babel-jest@^26.3.0:
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.3.0.tgz#10d0ca4b529ca3e7d1417855ef7d7bd6fc0c3463"
- integrity sha512-sxPnQGEyHAOPF8NcUsD0g7hDCnvLL2XyblRBcgrzTWBB/mAIpWow3n1bEL+VghnnZfreLhFSBsFluRoK2tRK4g==
+babel-jest@^26.5.2:
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.5.2.tgz#164f367a35946c6cf54eaccde8762dec50422250"
+ integrity sha512-U3KvymF3SczA3vOL/cgiUFOznfMET+XDIXiWnoJV45siAp2pLMG8i2+/MGZlAC3f/F6Q40LR4M4qDrWZ9wkK8A==
dependencies:
- "@jest/transform" "^26.3.0"
- "@jest/types" "^26.3.0"
+ "@jest/transform" "^26.5.2"
+ "@jest/types" "^26.5.2"
"@types/babel__core" "^7.1.7"
babel-plugin-istanbul "^6.0.0"
- babel-preset-jest "^26.3.0"
+ babel-preset-jest "^26.5.0"
chalk "^4.0.0"
graceful-fs "^4.2.4"
slash "^3.0.0"
@@ -2166,10 +2212,10 @@ babel-plugin-istanbul@^6.0.0:
istanbul-lib-instrument "^4.0.0"
test-exclude "^6.0.0"
-babel-plugin-jest-hoist@^26.2.0:
- version "26.2.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.2.0.tgz#bdd0011df0d3d513e5e95f76bd53b51147aca2dd"
- integrity sha512-B/hVMRv8Nh1sQ1a3EY8I0n4Y1Wty3NrR5ebOyVT302op+DOAau+xNEImGMsUWOC3++ZlMooCytKz+NgN8aKGbA==
+babel-plugin-jest-hoist@^26.5.0:
+ version "26.5.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.5.0.tgz#3916b3a28129c29528de91e5784a44680db46385"
+ integrity sha512-ck17uZFD3CDfuwCLATWZxkkuGGFhMij8quP8CNhwj8ek1mqFgbFzRJ30xwC04LLscj/aKsVFfRST+b5PT7rSuw==
dependencies:
"@babel/template" "^7.3.3"
"@babel/types" "^7.3.3"
@@ -2245,12 +2291,12 @@ babel-preset-current-node-syntax@^0.1.3:
"@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
"@babel/plugin-syntax-optional-chaining" "^7.8.3"
-babel-preset-jest@^26.3.0:
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.3.0.tgz#ed6344506225c065fd8a0b53e191986f74890776"
- integrity sha512-5WPdf7nyYi2/eRxCbVrE1kKCWxgWY4RsPEbdJWFm7QsesFGqjdkyLeu1zRkwM1cxK6EPIlNd6d2AxLk7J+t4pw==
+babel-preset-jest@^26.5.0:
+ version "26.5.0"
+ resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.5.0.tgz#f1b166045cd21437d1188d29f7fba470d5bdb0e7"
+ integrity sha512-F2vTluljhqkiGSJGBg/jOruA8vIIIL11YrxRcO7nviNTMbbofPSHwnm8mgP7d/wS7wRSexRoI6X1A6T74d4LQA==
dependencies:
- babel-plugin-jest-hoist "^26.2.0"
+ babel-plugin-jest-hoist "^26.5.0"
babel-preset-current-node-syntax "^0.1.3"
babel-runtime@^6.26.0:
@@ -2501,12 +2547,12 @@ browserify-zlib@^0.2.0:
pako "~1.0.5"
browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.8.5:
- version "4.14.3"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.3.tgz#381f9e7f13794b2eb17e1761b4f118e8ae665a53"
- integrity sha512-GcZPC5+YqyPO4SFnz48/B0YaCwS47Q9iPChRGi6t7HhflKBcINzFrJvRfC+jp30sRMKxF+d4EHGs27Z0XP1NaQ==
+ version "4.14.5"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.5.tgz#1c751461a102ddc60e40993639b709be7f2c4015"
+ integrity sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA==
dependencies:
- caniuse-lite "^1.0.30001131"
- electron-to-chromium "^1.3.570"
+ caniuse-lite "^1.0.30001135"
+ electron-to-chromium "^1.3.571"
escalade "^3.1.0"
node-releases "^1.1.61"
@@ -2676,10 +2722,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001131:
- version "1.0.30001133"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001133.tgz#ec564c5495311299eb05245e252d589a84acd95e"
- integrity sha512-s3XAUFaC/ntDb1O3lcw9K8MPeOW7KO3z9+GzAoBxfz1B0VdacXPMKgFUtG4KIsgmnbexmi013s9miVu4h+qMHw==
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001135:
+ version "1.0.30001143"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001143.tgz#560f2cfb9f313d1d7e52eb8dac0e4e36c8821c0d"
+ integrity sha512-p/PO5YbwmCpBJPxjOiKBvAlUPgF8dExhfEpnsH+ys4N/791WHrYrGg0cyHiAURl5hSbx5vIcjKmQAP6sHDYH3w==
capture-exit@^2.0.0:
version "2.0.0"
@@ -2721,7 +2767,7 @@ chalk@^3.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
-chalk@^4.0.0:
+chalk@^4.0.0, chalk@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
@@ -2739,10 +2785,10 @@ check-types@^8.0.3:
resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552"
integrity sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==
-"chokidar@>=2.0.0 <4.0.0":
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.0.tgz#b30611423ce376357c765b9b8f904b9fba3c0be8"
- integrity sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==
+"chokidar@>=2.0.0 <4.0.0", chokidar@^3.4.1:
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.1.tgz#e905bdecf10eaa0a0b1db0c664481cc4cbc22ba1"
+ integrity sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==
dependencies:
anymatch "~3.1.1"
braces "~3.0.2"
@@ -2773,21 +2819,6 @@ chokidar@^2.1.8:
optionalDependencies:
fsevents "^1.2.7"
-chokidar@^3.4.1:
- version "3.4.1"
- resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.1.tgz#e905bdecf10eaa0a0b1db0c664481cc4cbc22ba1"
- integrity sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==
- dependencies:
- anymatch "~3.1.1"
- braces "~3.0.2"
- glob-parent "~5.1.0"
- is-binary-path "~2.1.0"
- is-glob "~4.0.1"
- normalize-path "~3.0.0"
- readdirp "~3.4.0"
- optionalDependencies:
- fsevents "~2.1.2"
-
chownr@^1.1.1:
version "1.1.4"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
@@ -2988,15 +3019,15 @@ compressible@~2.0.16:
dependencies:
mime-db ">= 1.43.0 < 2"
-compression-webpack-plugin@^5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-5.0.1.tgz#316c1a4e4ccc94510a978c967fc143581d1e3643"
- integrity sha512-Wcb99O4UkdDZiM+blEw6h+cUfJYCn2kgK0l3fjLOm72Stso9DVMieQpBD4PVpyI7DLL6+zNh0iJV3p4HDwTinQ==
+compression-webpack-plugin@^6.0.3:
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-6.0.3.tgz#d0d3e913810e3bf67462e1cecd794b3109af89de"
+ integrity sha512-xzSWiZWwBs+HHGhlYxw0oFaYL/0VYErEqDHCAJhJ3Mza5fmF5JJ4iaB6Ap2JT68C0UhhmoI4Mh37LVz/THv2Fw==
dependencies:
cacache "^15.0.5"
find-cache-dir "^3.3.1"
- schema-utils "^2.7.0"
- serialize-javascript "^4.0.0"
+ schema-utils "^3.0.0"
+ serialize-javascript "^5.0.1"
webpack-sources "^1.4.3"
compression@^1.7.4:
@@ -3498,9 +3529,9 @@ decamelize@^1.2.0:
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
decimal.js@^10.2.0:
- version "10.2.0"
- resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231"
- integrity sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw==
+ version "10.2.1"
+ resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3"
+ integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==
decode-uri-component@^0.2.0:
version "0.2.0"
@@ -3642,10 +3673,10 @@ diff-sequences@^25.2.6:
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd"
integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==
-diff-sequences@^26.3.0:
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.3.0.tgz#62a59b1b29ab7fd27cef2a33ae52abe73042d0a2"
- integrity sha512-5j5vdRcw3CNctePNYN0Wy2e/JbWT6cAYnXv5OuqPhDpyCGc0uLu2TK0zOCJWNB9kOIfYMSpIulRaDgIi4HJ6Ig==
+diff-sequences@^26.5.0:
+ version "26.5.0"
+ resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.5.0.tgz#ef766cf09d43ed40406611f11c6d8d9dd8b2fefd"
+ integrity sha512-ZXx86srb/iYy6jG71k++wBN9P9J05UNQ5hQHQd9MtMPvcqXPx/vKU69jfHV637D00Q2gSgPk2D+jSx3l1lDW/Q==
diffie-hellman@^5.0.0:
version "5.0.3"
@@ -3698,10 +3729,10 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
-dom-accessibility-api@^0.4.5:
- version "0.4.7"
- resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.4.7.tgz#31d01c113af49f323409b3ed09e56967aba485a8"
- integrity sha512-5+GzhTpCQYHz4NjL8loYTDVBnXIjNLBadWQBKxXk+osFEplLt3EsSYBu2YZcdZ8QqrvCHgW6TSMGMbmgfhrn2g==
+dom-accessibility-api@^0.5.1:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.2.tgz#ef3cdb5d3f0d599d8f9c8b18df2fb63c9793739d"
+ integrity sha512-k7hRNKAiPJXD2aBqfahSo4/01cTsKWXf+LqJgglnkN2Nz8TsxXKQBXHhKe0Ye9fEfHEZY49uSA5Sr3AqP/sWKA==
dom-helpers@^3.2.1, dom-helpers@^3.4.0:
version "3.4.0"
@@ -3801,10 +3832,10 @@ ejs@^2.3.4, ejs@^2.6.1:
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba"
integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==
-electron-to-chromium@^1.3.570:
- version "1.3.570"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.570.tgz#3f5141cc39b4e3892a276b4889980dabf1d29c7f"
- integrity sha512-Y6OCoVQgFQBP5py6A/06+yWxUZHDlNr/gNDGatjH8AZqXl8X0tE4LfjLJsXGz/JmWJz8a6K7bR1k+QzZ+k//fg==
+electron-to-chromium@^1.3.571:
+ version "1.3.574"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.574.tgz#bdd87f62fe70165e5c862a0acf0cee9889e23aa3"
+ integrity sha512-kF8Bfe1h8X1pPwlw6oRoIXj0DevowviP6fl0wcljm+nZjy/7+Fos4THo1N/7dVGEJlyEqK9C8qNnbheH+Eazfw==
elliptic@^6.5.3:
version "6.5.3"
@@ -3908,37 +3939,37 @@ error-stack-parser@^2.0.6:
stackframe "^1.1.1"
es-abstract@^1.17.0, es-abstract@^1.17.0-next.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5:
- version "1.17.6"
- resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a"
- integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==
+ version "1.17.7"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c"
+ integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==
dependencies:
es-to-primitive "^1.2.1"
function-bind "^1.1.1"
has "^1.0.3"
has-symbols "^1.0.1"
- is-callable "^1.2.0"
- is-regex "^1.1.0"
- object-inspect "^1.7.0"
+ is-callable "^1.2.2"
+ is-regex "^1.1.1"
+ object-inspect "^1.8.0"
object-keys "^1.1.1"
- object.assign "^4.1.0"
+ object.assign "^4.1.1"
string.prototype.trimend "^1.0.1"
string.prototype.trimstart "^1.0.1"
-es-abstract@^1.18.0-next.0:
- version "1.18.0-next.0"
- resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.0.tgz#b302834927e624d8e5837ed48224291f2c66e6fc"
- integrity sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==
+es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1:
+ version "1.18.0-next.1"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68"
+ integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==
dependencies:
es-to-primitive "^1.2.1"
function-bind "^1.1.1"
has "^1.0.3"
has-symbols "^1.0.1"
- is-callable "^1.2.0"
+ is-callable "^1.2.2"
is-negative-zero "^2.0.0"
is-regex "^1.1.1"
object-inspect "^1.8.0"
object-keys "^1.1.1"
- object.assign "^4.1.0"
+ object.assign "^4.1.1"
string.prototype.trimend "^1.0.1"
string.prototype.trimstart "^1.0.1"
@@ -4060,7 +4091,7 @@ escope@^3.6.0:
esrecurse "^4.1.0"
estraverse "^4.1.1"
-eslint-import-resolver-node@^0.3.3:
+eslint-import-resolver-node@^0.3.4:
version "0.3.4"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717"
integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==
@@ -4076,17 +4107,17 @@ eslint-module-utils@^2.6.0:
debug "^2.6.9"
pkg-dir "^2.0.0"
-eslint-plugin-import@~2.22.0:
- version "2.22.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e"
- integrity sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==
+eslint-plugin-import@~2.22.1:
+ version "2.22.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz#0896c7e6a0cf44109a2d97b95903c2bb689d7702"
+ integrity sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==
dependencies:
array-includes "^3.1.1"
array.prototype.flat "^1.2.3"
contains-path "^0.1.0"
debug "^2.6.9"
doctrine "1.5.0"
- eslint-import-resolver-node "^0.3.3"
+ eslint-import-resolver-node "^0.3.4"
eslint-module-utils "^2.6.0"
has "^1.0.3"
minimatch "^3.0.4"
@@ -4117,16 +4148,16 @@ eslint-plugin-promise@~4.2.1:
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==
-eslint-plugin-react@~7.20.4:
- version "7.20.4"
- resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.20.4.tgz#c14d2631221ec694ddd84557d7152f44b66e4aa0"
- integrity sha512-y4DOQ0LrzuDQFEAnYFGjJMRHQQqfTco02qiWI00eGQYikHTzC15S5aRHGWSffnThv8sBpsmFBLky3K5keniAJg==
+eslint-plugin-react@~7.21.4:
+ version "7.21.4"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.21.4.tgz#31060b2e5ff82b12e24a3cc33edb7d12f904775c"
+ integrity sha512-uHeQ8A0hg0ltNDXFu3qSfFqTNPXm1XithH6/SY318UX76CMj7Q599qWpgmMhVQyvhq36pm7qvoN3pb6/3jsTFg==
dependencies:
array-includes "^3.1.1"
array.prototype.flatmap "^1.2.3"
doctrine "^2.1.0"
has "^1.0.3"
- jsx-ast-utils "^2.4.1"
+ jsx-ast-utils "^2.4.1 || ^3.0.0"
object.entries "^1.1.2"
object.fromentries "^2.0.2"
object.values "^1.1.1"
@@ -4142,12 +4173,12 @@ eslint-scope@^4.0.3:
esrecurse "^4.1.0"
estraverse "^4.1.1"
-eslint-scope@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5"
- integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==
+eslint-scope@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
+ integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
dependencies:
- esrecurse "^4.1.0"
+ esrecurse "^4.3.0"
estraverse "^4.1.1"
eslint-utils@^2.1.0:
@@ -4162,6 +4193,11 @@ eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
+eslint-visitor-keys@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
+ integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
+
eslint@^2.7.0:
version "2.13.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-2.13.1.tgz#e4cc8fa0f009fb829aaae23855a29360be1f6c11"
@@ -4201,22 +4237,23 @@ eslint@^2.7.0:
text-table "~0.2.0"
user-home "^2.0.0"
-eslint@^7.6.0:
- version "7.6.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.6.0.tgz#522d67cfaea09724d96949c70e7a0550614d64d6"
- integrity sha512-QlAManNtqr7sozWm5TF4wIH9gmUm2hE3vNRUvyoYAa4y1l5/jxD/PQStEjBMQtCqZmSep8UxrcecI60hOpe61w==
+eslint@^7.11.0:
+ version "7.11.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.11.0.tgz#aaf2d23a0b5f1d652a08edacea0c19f7fadc0b3b"
+ integrity sha512-G9+qtYVCHaDi1ZuWzBsOWo2wSwd70TXnU6UHA3cTYHp7gCTXZcpggWFoUVAMRarg68qtPoNfFbzPh+VdOgmwmw==
dependencies:
"@babel/code-frame" "^7.0.0"
+ "@eslint/eslintrc" "^0.1.3"
ajv "^6.10.0"
chalk "^4.0.0"
cross-spawn "^7.0.2"
debug "^4.0.1"
doctrine "^3.0.0"
enquirer "^2.3.5"
- eslint-scope "^5.1.0"
+ eslint-scope "^5.1.1"
eslint-utils "^2.1.0"
- eslint-visitor-keys "^1.3.0"
- espree "^7.2.0"
+ eslint-visitor-keys "^2.0.0"
+ espree "^7.3.0"
esquery "^1.2.0"
esutils "^2.0.2"
file-entry-cache "^5.0.1"
@@ -4251,12 +4288,12 @@ espree@^3.1.6:
acorn "^5.5.0"
acorn-jsx "^3.0.0"
-espree@^7.2.0:
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/espree/-/espree-7.2.0.tgz#1c263d5b513dbad0ac30c4991b93ac354e948d69"
- integrity sha512-H+cQ3+3JYRMEIOl87e7QdHX70ocly5iW4+dttuR8iYSPr/hXKFb+7dBsZ7+u1adC4VrnPlTkv0+OwuPnDop19g==
+espree@^7.3.0:
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.0.tgz#dc30437cf67947cf576121ebd780f15eeac72348"
+ integrity sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==
dependencies:
- acorn "^7.3.1"
+ acorn "^7.4.0"
acorn-jsx "^5.2.0"
eslint-visitor-keys "^1.3.0"
@@ -4272,7 +4309,7 @@ esquery@^1.2.0:
dependencies:
estraverse "^5.1.0"
-esrecurse@^4.1.0:
+esrecurse@^4.1.0, esrecurse@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
@@ -4400,16 +4437,16 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
dependencies:
homedir-polyfill "^1.0.1"
-expect@^26.4.2:
- version "26.4.2"
- resolved "https://registry.yarnpkg.com/expect/-/expect-26.4.2.tgz#36db120928a5a2d7d9736643032de32f24e1b2a1"
- integrity sha512-IlJ3X52Z0lDHm7gjEp+m76uX46ldH5VpqmU0006vqDju/285twh7zaWMRhs67VpQhBwjjMchk+p5aA0VkERCAA==
+expect@^26.5.3:
+ version "26.5.3"
+ resolved "https://registry.yarnpkg.com/expect/-/expect-26.5.3.tgz#89d9795036f7358b0a9a5243238eb8086482d741"
+ integrity sha512-kkpOhGRWGOr+TEFUnYAjfGvv35bfP+OlPtqPIJpOCR9DVtv8QV+p8zG0Edqafh80fsjeE+7RBcVUq1xApnYglw==
dependencies:
- "@jest/types" "^26.3.0"
+ "@jest/types" "^26.5.2"
ansi-styles "^4.0.0"
jest-get-type "^26.3.0"
- jest-matcher-utils "^26.4.2"
- jest-message-util "^26.3.0"
+ jest-matcher-utils "^26.5.2"
+ jest-message-util "^26.5.2"
jest-regex-util "^26.0.0"
express@^4.16.3, express@^4.17.1:
@@ -4568,13 +4605,13 @@ file-entry-cache@^5.0.1:
dependencies:
flat-cache "^2.0.1"
-file-loader@^6.1.0:
- version "6.1.0"
- resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.1.0.tgz#65b9fcfb0ea7f65a234a1f10cdd7f1ab9a33f253"
- integrity sha512-26qPdHyTsArQ6gU4P1HJbAbnFTyT2r0pG7czh1GFAd9TZbj0n94wWbupgixZH/ET/meqi2/5+F7DhW4OAXD+Lg==
+file-loader@^6.1.1:
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.1.1.tgz#a6f29dfb3f5933a1c350b2dbaa20ac5be0539baa"
+ integrity sha512-Klt8C4BjWSXYQAfhpYYkG4qHNTna4toMHEbWrI5IuVoxbU6uiDKeKAP99R8mmbJi3lvewn/jQBOgU4+NS3tDQw==
dependencies:
loader-utils "^2.0.0"
- schema-utils "^2.7.1"
+ schema-utils "^3.0.0"
file-type@^12.4.1:
version "12.4.2"
@@ -5281,10 +5318,10 @@ http-errors@~1.7.2:
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
-http-link-header@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/http-link-header/-/http-link-header-1.0.2.tgz#bea50f02e1c7996021f1013b428c63f77e0f4e11"
- integrity sha512-z6YOZ8ZEnejkcCWlGZzYXNa6i+ZaTfiTg3WhlV/YvnNya3W/RbX1bMVUMTuCrg/DrtTCQxaFCkXCz4FtLpcebg==
+http-link-header@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/http-link-header/-/http-link-header-1.0.3.tgz#abbc2cdc5e06dd7e196a4983adac08a2d085ec90"
+ integrity sha512-nARK1wSKoBBrtcoESlHBx36c1Ln/gnbNQi1eB6MeTUefJIT3NvUOsV15bClga0k38f0q/kN5xxrGSDS3EFnm9w==
"http-parser-js@>=0.4.0 <0.4.11":
version "0.4.10"
@@ -5388,7 +5425,7 @@ import-fresh@^2.0.0:
caller-path "^2.0.0"
resolve-from "^3.0.0"
-import-fresh@^3.0.0, import-fresh@^3.1.0:
+import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66"
integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==
@@ -5419,13 +5456,13 @@ import-local@^3.0.2:
pkg-dir "^4.2.0"
resolve-cwd "^3.0.0"
-imports-loader@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-1.1.0.tgz#1c3a388d0c5cd7f9eb08f3646d4aae3b70e57933"
- integrity sha512-HcPM6rULdQ6EBLVq+5O+CF9xb7qiUjsRm6V28bTG/c3IU5sQkVZzUDwYY0r4jHvSAmVFdO9WA/vLAURR5WQSeQ==
+imports-loader@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-1.2.0.tgz#b06823d0bb42e6f5ff89bc893829000eda46693f"
+ integrity sha512-zPvangKEgrrPeqeUqH0Uhc59YqK07JqZBi9a9cQ3v/EKUIqrbJHY4CvUrDus2lgQa5AmPyXuGrWP8JJTqzE5RQ==
dependencies:
loader-utils "^2.0.0"
- schema-utils "^2.7.0"
+ schema-utils "^3.0.0"
source-map "^0.6.1"
strip-comments "^2.0.1"
@@ -5639,10 +5676,10 @@ is-binary-path@~2.1.0:
dependencies:
binary-extensions "^2.0.0"
-is-callable@^1.1.4, is-callable@^1.2.0:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.1.tgz#4d1e21a4f437509d25ce55f8184350771421c96d"
- integrity sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==
+is-callable@^1.1.4, is-callable@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9"
+ integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==
is-ci@^2.0.0:
version "2.0.0"
@@ -5861,7 +5898,7 @@ is-regex@^1.0.4:
dependencies:
has-symbols "^1.0.1"
-is-regex@^1.1.0, is-regex@^1.1.1:
+is-regex@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9"
integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==
@@ -6002,57 +6039,57 @@ istanbul-reports@^3.0.2:
html-escaper "^2.0.0"
istanbul-lib-report "^3.0.0"
-jest-changed-files@^26.3.0:
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.3.0.tgz#68fb2a7eb125f50839dab1f5a17db3607fe195b1"
- integrity sha512-1C4R4nijgPltX6fugKxM4oQ18zimS7LqQ+zTTY8lMCMFPrxqBFb7KJH0Z2fRQJvw2Slbaipsqq7s1mgX5Iot+g==
+jest-changed-files@^26.5.2:
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.5.2.tgz#330232c6a5c09a7f040a5870e8f0a9c6abcdbed5"
+ integrity sha512-qSmssmiIdvM5BWVtyK/nqVpN3spR5YyvkvPqz1x3BR1bwIxsWmU/MGwLoCrPNLbkG2ASAKfvmJpOduEApBPh2w==
dependencies:
- "@jest/types" "^26.3.0"
+ "@jest/types" "^26.5.2"
execa "^4.0.0"
throat "^5.0.0"
-jest-cli@^26.4.2:
- version "26.4.2"
- resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.4.2.tgz#24afc6e4dfc25cde4c7ec4226fb7db5f157c21da"
- integrity sha512-zb+lGd/SfrPvoRSC/0LWdaWCnscXc1mGYW//NP4/tmBvRPT3VntZ2jtKUONsRi59zc5JqmsSajA9ewJKFYp8Cw==
+jest-cli@^26.5.3:
+ version "26.5.3"
+ resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.5.3.tgz#f936b98f247b76b7bc89c7af50af82c88e356a80"
+ integrity sha512-HkbSvtugpSXBf2660v9FrNVUgxvPkssN8CRGj9gPM8PLhnaa6zziFiCEKQAkQS4uRzseww45o0TR+l6KeRYV9A==
dependencies:
- "@jest/core" "^26.4.2"
- "@jest/test-result" "^26.3.0"
- "@jest/types" "^26.3.0"
+ "@jest/core" "^26.5.3"
+ "@jest/test-result" "^26.5.2"
+ "@jest/types" "^26.5.2"
chalk "^4.0.0"
exit "^0.1.2"
graceful-fs "^4.2.4"
import-local "^3.0.2"
is-ci "^2.0.0"
- jest-config "^26.4.2"
- jest-util "^26.3.0"
- jest-validate "^26.4.2"
+ jest-config "^26.5.3"
+ jest-util "^26.5.2"
+ jest-validate "^26.5.3"
prompts "^2.0.1"
- yargs "^15.3.1"
+ yargs "^15.4.1"
-jest-config@^26.4.2:
- version "26.4.2"
- resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.4.2.tgz#da0cbb7dc2c131ffe831f0f7f2a36256e6086558"
- integrity sha512-QBf7YGLuToiM8PmTnJEdRxyYy3mHWLh24LJZKVdXZ2PNdizSe1B/E8bVm+HYcjbEzGuVXDv/di+EzdO/6Gq80A==
+jest-config@^26.5.3:
+ version "26.5.3"
+ resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.5.3.tgz#baf51c9be078c2c755c8f8a51ec0f06c762c1d3f"
+ integrity sha512-NVhZiIuN0GQM6b6as4CI5FSCyXKxdrx5ACMCcv/7Pf+TeCajJhJc+6dwgdAVPyerUFB9pRBIz3bE7clSrRge/w==
dependencies:
"@babel/core" "^7.1.0"
- "@jest/test-sequencer" "^26.4.2"
- "@jest/types" "^26.3.0"
- babel-jest "^26.3.0"
+ "@jest/test-sequencer" "^26.5.3"
+ "@jest/types" "^26.5.2"
+ babel-jest "^26.5.2"
chalk "^4.0.0"
deepmerge "^4.2.2"
glob "^7.1.1"
graceful-fs "^4.2.4"
- jest-environment-jsdom "^26.3.0"
- jest-environment-node "^26.3.0"
+ jest-environment-jsdom "^26.5.2"
+ jest-environment-node "^26.5.2"
jest-get-type "^26.3.0"
- jest-jasmine2 "^26.4.2"
+ jest-jasmine2 "^26.5.3"
jest-regex-util "^26.0.0"
- jest-resolve "^26.4.0"
- jest-util "^26.3.0"
- jest-validate "^26.4.2"
+ jest-resolve "^26.5.2"
+ jest-util "^26.5.2"
+ jest-validate "^26.5.3"
micromatch "^4.0.2"
- pretty-format "^26.4.2"
+ pretty-format "^26.5.2"
jest-diff@^25.2.1:
version "25.5.0"
@@ -6064,15 +6101,15 @@ jest-diff@^25.2.1:
jest-get-type "^25.2.6"
pretty-format "^25.5.0"
-jest-diff@^26.4.2:
- version "26.4.2"
- resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.4.2.tgz#a1b7b303bcc534aabdb3bd4a7caf594ac059f5aa"
- integrity sha512-6T1XQY8U28WH0Z5rGpQ+VqZSZz8EN8rZcBtfvXaOkbwxIEeRre6qnuZQlbY1AJ4MKDxQF8EkrCvK+hL/VkyYLQ==
+jest-diff@^26.5.2:
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.5.2.tgz#8e26cb32dc598e8b8a1b9deff55316f8313c8053"
+ integrity sha512-HCSWDUGwsov5oTlGzrRM+UPJI/Dpqi9jzeV0fdRNi3Ch5bnoXhnyJMmVg2juv9081zLIy3HGPI5mcuGgXM2xRA==
dependencies:
chalk "^4.0.0"
- diff-sequences "^26.3.0"
+ diff-sequences "^26.5.0"
jest-get-type "^26.3.0"
- pretty-format "^26.4.2"
+ pretty-format "^26.5.2"
jest-docblock@^26.0.0:
version "26.0.0"
@@ -6081,41 +6118,41 @@ jest-docblock@^26.0.0:
dependencies:
detect-newline "^3.0.0"
-jest-each@^26.4.2:
- version "26.4.2"
- resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.4.2.tgz#bb14f7f4304f2bb2e2b81f783f989449b8b6ffae"
- integrity sha512-p15rt8r8cUcRY0Mvo1fpkOGYm7iI8S6ySxgIdfh3oOIv+gHwrHTy5VWCGOecWUhDsit4Nz8avJWdT07WLpbwDA==
+jest-each@^26.5.2:
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.5.2.tgz#35e68d6906a7f826d3ca5803cfe91d17a5a34c31"
+ integrity sha512-w7D9FNe0m2D3yZ0Drj9CLkyF/mGhmBSULMQTypzAKR746xXnjUrK8GUJdlLTWUF6dd0ks3MtvGP7/xNFr9Aphg==
dependencies:
- "@jest/types" "^26.3.0"
+ "@jest/types" "^26.5.2"
chalk "^4.0.0"
jest-get-type "^26.3.0"
- jest-util "^26.3.0"
- pretty-format "^26.4.2"
+ jest-util "^26.5.2"
+ pretty-format "^26.5.2"
-jest-environment-jsdom@^26.3.0:
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.3.0.tgz#3b749ba0f3a78e92ba2c9ce519e16e5dd515220c"
- integrity sha512-zra8He2btIMJkAzvLaiZ9QwEPGEetbxqmjEBQwhH3CA+Hhhu0jSiEJxnJMbX28TGUvPLxBt/zyaTLrOPF4yMJA==
+jest-environment-jsdom@^26.5.2:
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.5.2.tgz#5feab05b828fd3e4b96bee5e0493464ddd2bb4bc"
+ integrity sha512-fWZPx0bluJaTQ36+PmRpvUtUlUFlGGBNyGX1SN3dLUHHMcQ4WseNEzcGGKOw4U5towXgxI4qDoI3vwR18H0RTw==
dependencies:
- "@jest/environment" "^26.3.0"
- "@jest/fake-timers" "^26.3.0"
- "@jest/types" "^26.3.0"
+ "@jest/environment" "^26.5.2"
+ "@jest/fake-timers" "^26.5.2"
+ "@jest/types" "^26.5.2"
"@types/node" "*"
- jest-mock "^26.3.0"
- jest-util "^26.3.0"
- jsdom "^16.2.2"
-
-jest-environment-node@^26.3.0:
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.3.0.tgz#56c6cfb506d1597f94ee8d717072bda7228df849"
- integrity sha512-c9BvYoo+FGcMj5FunbBgtBnbR5qk3uky8PKyRVpSfe2/8+LrNQMiXX53z6q2kY+j15SkjQCOSL/6LHnCPLVHNw==
- dependencies:
- "@jest/environment" "^26.3.0"
- "@jest/fake-timers" "^26.3.0"
- "@jest/types" "^26.3.0"
+ jest-mock "^26.5.2"
+ jest-util "^26.5.2"
+ jsdom "^16.4.0"
+
+jest-environment-node@^26.5.2:
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.5.2.tgz#275a0f01b5e47447056f1541a15ed4da14acca03"
+ integrity sha512-YHjnDsf/GKFCYMGF1V+6HF7jhY1fcLfLNBDjhAOvFGvt6d8vXvNdJGVM7uTZ2VO/TuIyEFhPGaXMX5j3h7fsrA==
+ dependencies:
+ "@jest/environment" "^26.5.2"
+ "@jest/fake-timers" "^26.5.2"
+ "@jest/types" "^26.5.2"
"@types/node" "*"
- jest-mock "^26.3.0"
- jest-util "^26.3.0"
+ jest-mock "^26.5.2"
+ jest-util "^26.5.2"
jest-get-type@^25.2.6:
version "25.2.6"
@@ -6127,89 +6164,89 @@ jest-get-type@^26.3.0:
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0"
integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==
-jest-haste-map@^26.3.0:
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.3.0.tgz#c51a3b40100d53ab777bfdad382d2e7a00e5c726"
- integrity sha512-DHWBpTJgJhLLGwE5Z1ZaqLTYqeODQIZpby0zMBsCU9iRFHYyhklYqP4EiG73j5dkbaAdSZhgB938mL51Q5LeZA==
+jest-haste-map@^26.5.2:
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.5.2.tgz#a15008abfc502c18aa56e4919ed8c96304ceb23d"
+ integrity sha512-lJIAVJN3gtO3k4xy+7i2Xjtwh8CfPcH08WYjZpe9xzveDaqGw9fVNCpkYu6M525wKFVkLmyi7ku+DxCAP1lyMA==
dependencies:
- "@jest/types" "^26.3.0"
+ "@jest/types" "^26.5.2"
"@types/graceful-fs" "^4.1.2"
"@types/node" "*"
anymatch "^3.0.3"
fb-watchman "^2.0.0"
graceful-fs "^4.2.4"
jest-regex-util "^26.0.0"
- jest-serializer "^26.3.0"
- jest-util "^26.3.0"
- jest-worker "^26.3.0"
+ jest-serializer "^26.5.0"
+ jest-util "^26.5.2"
+ jest-worker "^26.5.0"
micromatch "^4.0.2"
sane "^4.0.3"
walker "^1.0.7"
optionalDependencies:
fsevents "^2.1.2"
-jest-jasmine2@^26.4.2:
- version "26.4.2"
- resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.4.2.tgz#18a9d5bec30904267ac5e9797570932aec1e2257"
- integrity sha512-z7H4EpCldHN1J8fNgsja58QftxBSL+JcwZmaXIvV9WKIM+x49F4GLHu/+BQh2kzRKHAgaN/E82od+8rTOBPyPA==
+jest-jasmine2@^26.5.3:
+ version "26.5.3"
+ resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.5.3.tgz#baad2114ce32d16aff25aeb877d18bb4e332dc4c"
+ integrity sha512-nFlZOpnGlNc7y/+UkkeHnvbOM+rLz4wB1AimgI9QhtnqSZte0wYjbAm8hf7TCwXlXgDwZxAXo6z0a2Wzn9FoOg==
dependencies:
"@babel/traverse" "^7.1.0"
- "@jest/environment" "^26.3.0"
- "@jest/source-map" "^26.3.0"
- "@jest/test-result" "^26.3.0"
- "@jest/types" "^26.3.0"
+ "@jest/environment" "^26.5.2"
+ "@jest/source-map" "^26.5.0"
+ "@jest/test-result" "^26.5.2"
+ "@jest/types" "^26.5.2"
"@types/node" "*"
chalk "^4.0.0"
co "^4.6.0"
- expect "^26.4.2"
+ expect "^26.5.3"
is-generator-fn "^2.0.0"
- jest-each "^26.4.2"
- jest-matcher-utils "^26.4.2"
- jest-message-util "^26.3.0"
- jest-runtime "^26.4.2"
- jest-snapshot "^26.4.2"
- jest-util "^26.3.0"
- pretty-format "^26.4.2"
+ jest-each "^26.5.2"
+ jest-matcher-utils "^26.5.2"
+ jest-message-util "^26.5.2"
+ jest-runtime "^26.5.3"
+ jest-snapshot "^26.5.3"
+ jest-util "^26.5.2"
+ pretty-format "^26.5.2"
throat "^5.0.0"
-jest-leak-detector@^26.4.2:
- version "26.4.2"
- resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.4.2.tgz#c73e2fa8757bf905f6f66fb9e0070b70fa0f573f"
- integrity sha512-akzGcxwxtE+9ZJZRW+M2o+nTNnmQZxrHJxX/HjgDaU5+PLmY1qnQPnMjgADPGCRPhB+Yawe1iij0REe+k/aHoA==
+jest-leak-detector@^26.5.2:
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.5.2.tgz#83fcf9a4a6ef157549552cb4f32ca1d6221eea69"
+ integrity sha512-h7ia3dLzBFItmYERaLPEtEKxy3YlcbcRSjj0XRNJgBEyODuu+3DM2o62kvIFvs3PsaYoIIv+e+nLRI61Dj1CNw==
dependencies:
jest-get-type "^26.3.0"
- pretty-format "^26.4.2"
+ pretty-format "^26.5.2"
-jest-matcher-utils@^26.4.2:
- version "26.4.2"
- resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.4.2.tgz#fa81f3693f7cb67e5fc1537317525ef3b85f4b06"
- integrity sha512-KcbNqWfWUG24R7tu9WcAOKKdiXiXCbMvQYT6iodZ9k1f7065k0keUOW6XpJMMvah+hTfqkhJhRXmA3r3zMAg0Q==
+jest-matcher-utils@^26.5.2:
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.5.2.tgz#6aa2c76ce8b9c33e66f8856ff3a52bab59e6c85a"
+ integrity sha512-W9GO9KBIC4gIArsNqDUKsLnhivaqf8MSs6ujO/JDcPIQrmY+aasewweXVET8KdrJ6ADQaUne5UzysvF/RR7JYA==
dependencies:
chalk "^4.0.0"
- jest-diff "^26.4.2"
+ jest-diff "^26.5.2"
jest-get-type "^26.3.0"
- pretty-format "^26.4.2"
+ pretty-format "^26.5.2"
-jest-message-util@^26.3.0:
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.3.0.tgz#3bdb538af27bb417f2d4d16557606fd082d5841a"
- integrity sha512-xIavRYqr4/otGOiLxLZGj3ieMmjcNE73Ui+LdSW/Y790j5acqCsAdDiLIbzHCZMpN07JOENRWX5DcU+OQ+TjTA==
+jest-message-util@^26.5.2:
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.5.2.tgz#6c4c4c46dcfbabb47cd1ba2f6351559729bc11bb"
+ integrity sha512-Ocp9UYZ5Jl15C5PNsoDiGEk14A4NG0zZKknpWdZGoMzJuGAkVt10e97tnEVMYpk7LnQHZOfuK2j/izLBMcuCZw==
dependencies:
"@babel/code-frame" "^7.0.0"
- "@jest/types" "^26.3.0"
- "@types/stack-utils" "^1.0.1"
+ "@jest/types" "^26.5.2"
+ "@types/stack-utils" "^2.0.0"
chalk "^4.0.0"
graceful-fs "^4.2.4"
micromatch "^4.0.2"
slash "^3.0.0"
stack-utils "^2.0.2"
-jest-mock@^26.3.0:
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.3.0.tgz#ee62207c3c5ebe5f35b760e1267fee19a1cfdeba"
- integrity sha512-PeaRrg8Dc6mnS35gOo/CbZovoDPKAeB1FICZiuagAgGvbWdNNyjQjkOaGUa/3N3JtpQ/Mh9P4A2D4Fv51NnP8Q==
+jest-mock@^26.5.2:
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.5.2.tgz#c9302e8ef807f2bfc749ee52e65ad11166a1b6a1"
+ integrity sha512-9SiU4b5PtO51v0MtJwVRqeGEroH66Bnwtq4ARdNP7jNXbpT7+ByeWNAk4NeT/uHfNSVDXEXgQo1XRuwEqS6Rdw==
dependencies:
- "@jest/types" "^26.3.0"
+ "@jest/types" "^26.5.2"
"@types/node" "*"
jest-pnp-resolver@^1.2.2:
@@ -6222,170 +6259,171 @@ jest-regex-util@^26.0.0:
resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28"
integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==
-jest-resolve-dependencies@^26.4.2:
- version "26.4.2"
- resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.4.2.tgz#739bdb027c14befb2fe5aabbd03f7bab355f1dc5"
- integrity sha512-ADHaOwqEcVc71uTfySzSowA/RdxUpCxhxa2FNLiin9vWLB1uLPad3we+JSSROq5+SrL9iYPdZZF8bdKM7XABTQ==
+jest-resolve-dependencies@^26.5.3:
+ version "26.5.3"
+ resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.5.3.tgz#11483f91e534bdcd257ab21e8622799e59701aba"
+ integrity sha512-+KMDeke/BFK+mIQ2IYSyBz010h7zQaVt4Xie6cLqUGChorx66vVeQVv4ErNoMwInnyYHi1Ud73tDS01UbXbfLQ==
dependencies:
- "@jest/types" "^26.3.0"
+ "@jest/types" "^26.5.2"
jest-regex-util "^26.0.0"
- jest-snapshot "^26.4.2"
+ jest-snapshot "^26.5.3"
-jest-resolve@^26.4.0:
- version "26.4.0"
- resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.4.0.tgz#6dc0af7fb93e65b73fec0368ca2b76f3eb59a6d7"
- integrity sha512-bn/JoZTEXRSlEx3+SfgZcJAVuTMOksYq9xe9O6s4Ekg84aKBObEaVXKOEilULRqviSLAYJldnoWV9c07kwtiCg==
+jest-resolve@^26.5.2:
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.5.2.tgz#0d719144f61944a428657b755a0e5c6af4fc8602"
+ integrity sha512-XsPxojXGRA0CoDD7Vis59ucz2p3cQFU5C+19tz3tLEAlhYKkK77IL0cjYjikY9wXnOaBeEdm1rOgSJjbZWpcZg==
dependencies:
- "@jest/types" "^26.3.0"
+ "@jest/types" "^26.5.2"
chalk "^4.0.0"
graceful-fs "^4.2.4"
jest-pnp-resolver "^1.2.2"
- jest-util "^26.3.0"
+ jest-util "^26.5.2"
read-pkg-up "^7.0.1"
resolve "^1.17.0"
slash "^3.0.0"
-jest-runner@^26.4.2:
- version "26.4.2"
- resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.4.2.tgz#c3ec5482c8edd31973bd3935df5a449a45b5b853"
- integrity sha512-FgjDHeVknDjw1gRAYaoUoShe1K3XUuFMkIaXbdhEys+1O4bEJS8Avmn4lBwoMfL8O5oFTdWYKcf3tEJyyYyk8g==
+jest-runner@^26.5.3:
+ version "26.5.3"
+ resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.5.3.tgz#800787459ea59c68e7505952933e33981dc3db38"
+ integrity sha512-qproP0Pq7IIule+263W57k2+8kWCszVJTC9TJWGUz0xJBr+gNiniGXlG8rotd0XxwonD5UiJloYoSO5vbUr5FQ==
dependencies:
- "@jest/console" "^26.3.0"
- "@jest/environment" "^26.3.0"
- "@jest/test-result" "^26.3.0"
- "@jest/types" "^26.3.0"
+ "@jest/console" "^26.5.2"
+ "@jest/environment" "^26.5.2"
+ "@jest/test-result" "^26.5.2"
+ "@jest/types" "^26.5.2"
"@types/node" "*"
chalk "^4.0.0"
emittery "^0.7.1"
exit "^0.1.2"
graceful-fs "^4.2.4"
- jest-config "^26.4.2"
+ jest-config "^26.5.3"
jest-docblock "^26.0.0"
- jest-haste-map "^26.3.0"
- jest-leak-detector "^26.4.2"
- jest-message-util "^26.3.0"
- jest-resolve "^26.4.0"
- jest-runtime "^26.4.2"
- jest-util "^26.3.0"
- jest-worker "^26.3.0"
+ jest-haste-map "^26.5.2"
+ jest-leak-detector "^26.5.2"
+ jest-message-util "^26.5.2"
+ jest-resolve "^26.5.2"
+ jest-runtime "^26.5.3"
+ jest-util "^26.5.2"
+ jest-worker "^26.5.0"
source-map-support "^0.5.6"
throat "^5.0.0"
-jest-runtime@^26.4.2:
- version "26.4.2"
- resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.4.2.tgz#94ce17890353c92e4206580c73a8f0c024c33c42"
- integrity sha512-4Pe7Uk5a80FnbHwSOk7ojNCJvz3Ks2CNQWT5Z7MJo4tX0jb3V/LThKvD9tKPNVNyeMH98J/nzGlcwc00R2dSHQ==
- dependencies:
- "@jest/console" "^26.3.0"
- "@jest/environment" "^26.3.0"
- "@jest/fake-timers" "^26.3.0"
- "@jest/globals" "^26.4.2"
- "@jest/source-map" "^26.3.0"
- "@jest/test-result" "^26.3.0"
- "@jest/transform" "^26.3.0"
- "@jest/types" "^26.3.0"
+jest-runtime@^26.5.3:
+ version "26.5.3"
+ resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.5.3.tgz#5882ae91fd88304310f069549e6bf82f3f198bea"
+ integrity sha512-IDjalmn2s/Tc4GvUwhPHZ0iaXCdMRq5p6taW9P8RpU+FpG01O3+H8z+p3rDCQ9mbyyyviDgxy/LHPLzrIOKBkQ==
+ dependencies:
+ "@jest/console" "^26.5.2"
+ "@jest/environment" "^26.5.2"
+ "@jest/fake-timers" "^26.5.2"
+ "@jest/globals" "^26.5.3"
+ "@jest/source-map" "^26.5.0"
+ "@jest/test-result" "^26.5.2"
+ "@jest/transform" "^26.5.2"
+ "@jest/types" "^26.5.2"
"@types/yargs" "^15.0.0"
chalk "^4.0.0"
collect-v8-coverage "^1.0.0"
exit "^0.1.2"
glob "^7.1.3"
graceful-fs "^4.2.4"
- jest-config "^26.4.2"
- jest-haste-map "^26.3.0"
- jest-message-util "^26.3.0"
- jest-mock "^26.3.0"
+ jest-config "^26.5.3"
+ jest-haste-map "^26.5.2"
+ jest-message-util "^26.5.2"
+ jest-mock "^26.5.2"
jest-regex-util "^26.0.0"
- jest-resolve "^26.4.0"
- jest-snapshot "^26.4.2"
- jest-util "^26.3.0"
- jest-validate "^26.4.2"
+ jest-resolve "^26.5.2"
+ jest-snapshot "^26.5.3"
+ jest-util "^26.5.2"
+ jest-validate "^26.5.3"
slash "^3.0.0"
strip-bom "^4.0.0"
- yargs "^15.3.1"
+ yargs "^15.4.1"
-jest-serializer@^26.3.0:
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.3.0.tgz#1c9d5e1b74d6e5f7e7f9627080fa205d976c33ef"
- integrity sha512-IDRBQBLPlKa4flg77fqg0n/pH87tcRKwe8zxOVTWISxGpPHYkRZ1dXKyh04JOja7gppc60+soKVZ791mruVdow==
+jest-serializer@^26.5.0:
+ version "26.5.0"
+ resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.5.0.tgz#f5425cc4c5f6b4b355f854b5f0f23ec6b962bc13"
+ integrity sha512-+h3Gf5CDRlSLdgTv7y0vPIAoLgX/SI7T4v6hy+TEXMgYbv+ztzbg5PSN6mUXAT/hXYHvZRWm+MaObVfqkhCGxA==
dependencies:
"@types/node" "*"
graceful-fs "^4.2.4"
-jest-snapshot@^26.4.2:
- version "26.4.2"
- resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.4.2.tgz#87d3ac2f2bd87ea8003602fbebd8fcb9e94104f6"
- integrity sha512-N6Uub8FccKlf5SBFnL2Ri/xofbaA68Cc3MGjP/NuwgnsvWh+9hLIR/DhrxbSiKXMY9vUW5dI6EW1eHaDHqe9sg==
+jest-snapshot@^26.5.3:
+ version "26.5.3"
+ resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.5.3.tgz#f6b4b4b845f85d4b0dadd7cf119c55d0c1688601"
+ integrity sha512-ZgAk0Wm0JJ75WS4lGaeRfa0zIgpL0KD595+XmtwlIEMe8j4FaYHyZhP1LNOO+8fXq7HJ3hll54+sFV9X4+CGVw==
dependencies:
"@babel/types" "^7.0.0"
- "@jest/types" "^26.3.0"
+ "@jest/types" "^26.5.2"
+ "@types/babel__traverse" "^7.0.4"
"@types/prettier" "^2.0.0"
chalk "^4.0.0"
- expect "^26.4.2"
+ expect "^26.5.3"
graceful-fs "^4.2.4"
- jest-diff "^26.4.2"
+ jest-diff "^26.5.2"
jest-get-type "^26.3.0"
- jest-haste-map "^26.3.0"
- jest-matcher-utils "^26.4.2"
- jest-message-util "^26.3.0"
- jest-resolve "^26.4.0"
+ jest-haste-map "^26.5.2"
+ jest-matcher-utils "^26.5.2"
+ jest-message-util "^26.5.2"
+ jest-resolve "^26.5.2"
natural-compare "^1.4.0"
- pretty-format "^26.4.2"
+ pretty-format "^26.5.2"
semver "^7.3.2"
-jest-util@^26.3.0:
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.3.0.tgz#a8974b191df30e2bf523ebbfdbaeb8efca535b3e"
- integrity sha512-4zpn6bwV0+AMFN0IYhH/wnzIQzRaYVrz1A8sYnRnj4UXDXbOVtWmlaZkO9mipFqZ13okIfN87aDoJWB7VH6hcw==
+jest-util@^26.5.2:
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.5.2.tgz#8403f75677902cc52a1b2140f568e91f8ed4f4d7"
+ integrity sha512-WTL675bK+GSSAYgS8z9FWdCT2nccO1yTIplNLPlP0OD8tUk/H5IrWKMMRudIQQ0qp8bb4k+1Qa8CxGKq9qnYdg==
dependencies:
- "@jest/types" "^26.3.0"
+ "@jest/types" "^26.5.2"
"@types/node" "*"
chalk "^4.0.0"
graceful-fs "^4.2.4"
is-ci "^2.0.0"
micromatch "^4.0.2"
-jest-validate@^26.4.2:
- version "26.4.2"
- resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.4.2.tgz#e871b0dfe97747133014dcf6445ee8018398f39c"
- integrity sha512-blft+xDX7XXghfhY0mrsBCYhX365n8K5wNDC4XAcNKqqjEzsRUSXP44m6PL0QJEW2crxQFLLztVnJ4j7oPlQrQ==
+jest-validate@^26.5.3:
+ version "26.5.3"
+ resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.5.3.tgz#eefd5a5c87059550548c5ad8d6589746c66929e3"
+ integrity sha512-LX07qKeAtY+lsU0o3IvfDdN5KH9OulEGOMN1sFo6PnEf5/qjS1LZIwNk9blcBeW94pQUI9dLN9FlDYDWI5tyaA==
dependencies:
- "@jest/types" "^26.3.0"
+ "@jest/types" "^26.5.2"
camelcase "^6.0.0"
chalk "^4.0.0"
jest-get-type "^26.3.0"
leven "^3.1.0"
- pretty-format "^26.4.2"
+ pretty-format "^26.5.2"
-jest-watcher@^26.3.0:
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.3.0.tgz#f8ef3068ddb8af160ef868400318dc4a898eed08"
- integrity sha512-XnLdKmyCGJ3VoF6G/p5ohbJ04q/vv5aH9ENI+i6BL0uu9WWB6Z7Z2lhQQk0d2AVZcRGp1yW+/TsoToMhBFPRdQ==
+jest-watcher@^26.5.2:
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.5.2.tgz#2957f4461007e0769d74b537379ecf6b7c696916"
+ integrity sha512-i3m1NtWzF+FXfJ3ljLBB/WQEp4uaNhX7QcQUWMokcifFTUQBDFyUMEwk0JkJ1kopHbx7Een3KX0Q7+9koGM/Pw==
dependencies:
- "@jest/test-result" "^26.3.0"
- "@jest/types" "^26.3.0"
+ "@jest/test-result" "^26.5.2"
+ "@jest/types" "^26.5.2"
"@types/node" "*"
ansi-escapes "^4.2.1"
chalk "^4.0.0"
- jest-util "^26.3.0"
+ jest-util "^26.5.2"
string-length "^4.0.1"
-jest-worker@^26.3.0:
- version "26.3.0"
- resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.3.0.tgz#7c8a97e4f4364b4f05ed8bca8ca0c24de091871f"
- integrity sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw==
+jest-worker@^26.5.0:
+ version "26.5.0"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.5.0.tgz#87deee86dbbc5f98d9919e0dadf2c40e3152fa30"
+ integrity sha512-kTw66Dn4ZX7WpjZ7T/SUDgRhapFRKWmisVAF0Rv4Fu8SLFD7eLbqpLvbxVqYhSgaWa7I+bW7pHnbyfNsH6stug==
dependencies:
"@types/node" "*"
merge-stream "^2.0.0"
supports-color "^7.0.0"
-jest@^26.4.2:
- version "26.4.2"
- resolved "https://registry.yarnpkg.com/jest/-/jest-26.4.2.tgz#7e8bfb348ec33f5459adeaffc1a25d5752d9d312"
- integrity sha512-LLCjPrUh98Ik8CzW8LLVnSCfLaiY+wbK53U7VxnFSX7Q+kWC4noVeDvGWIFw0Amfq1lq2VfGm7YHWSLBV62MJw==
+jest@^26.5.3:
+ version "26.5.3"
+ resolved "https://registry.yarnpkg.com/jest/-/jest-26.5.3.tgz#5e7a322d16f558dc565ca97639e85993ef5affe6"
+ integrity sha512-uJi3FuVSLmkZrWvaDyaVTZGLL8WcfynbRnFXyAHuEtYiSZ+ijDDIMOw1ytmftK+y/+OdAtsG9QrtbF7WIBmOyA==
dependencies:
- "@jest/core" "^26.4.2"
+ "@jest/core" "^26.5.3"
import-local "^3.0.2"
- jest-cli "^26.4.2"
+ jest-cli "^26.5.3"
js-base64@^2.1.9:
version "2.6.4"
@@ -6415,7 +6453,7 @@ jsbn@~0.1.0:
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
-jsdom@^16.2.2:
+jsdom@^16.4.0:
version "16.4.0"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.4.0.tgz#36005bde2d136f73eee1a830c6d45e55408edddb"
integrity sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==
@@ -6555,6 +6593,14 @@ jsx-ast-utils@^2.4.1:
array-includes "^3.1.1"
object.assign "^4.1.0"
+"jsx-ast-utils@^2.4.1 || ^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.0.0.tgz#0f49d5093bafa4b45d3fe02147d8b40ffc6c7438"
+ integrity sha512-sPuicm6EPKYI/UnWpOatvg4pI50qaBo4dSOMGUPutmJ26ttedFKXr0It0XXPk4HKnQ/1X0st4eSS2w2jhFk9Ow==
+ dependencies:
+ array-includes "^3.1.1"
+ object.assign "^4.1.1"
+
keycode@^2.1.7:
version "2.2.0"
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04"
@@ -6575,10 +6621,10 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
-klona@^2.0.3:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.3.tgz#98274552c513583ad7a01456a789a2a0b4a2a538"
- integrity sha512-CgPOT3ZadDpXxKcfV56lEQ9OQSZ42Mk26gnozI+uN/k39vzD8toUhRQoqsX0m9Q3eMPEfsLWmtyUpK/yqST4yg==
+klona@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0"
+ integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==
knot.js@^1.1.5:
version "1.1.5"
@@ -6963,10 +7009,10 @@ min-indent@^1.0.0:
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
-mini-css-extract-plugin@^0.11.0:
- version "0.11.0"
- resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.0.tgz#3918953075109d4ca204bf1e8a393a78d3cc821f"
- integrity sha512-dVWGuWJlQw2lZxsxBI3hOsoxg1k3DruLR0foHQLSkQMfk+qLJbv9dUk8fjmjWQKN9ef2n54ehA2FjClAsQhrWQ==
+mini-css-extract-plugin@^0.11.3:
+ version "0.11.3"
+ resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz#15b0910a7f32e62ffde4a7430cfefbd700724ea6"
+ integrity sha512-n9BA8LonkOkW1/zn+IbLPQmovsL0wMb9yx75fMJQZf2X1Zoec9yTZtyMePcyu19wPkmFbzZZA6fLTotpFhQsOA==
dependencies:
loader-utils "^1.1.0"
normalize-url "1.9.1"
@@ -7352,18 +7398,18 @@ object-fit-images@^3.2.3:
resolved "https://registry.yarnpkg.com/object-fit-images/-/object-fit-images-3.2.4.tgz#6c299d38fdf207746e5d2d46c2877f6f25d15b52"
integrity sha512-G+7LzpYfTfqUyrZlfrou/PLLLAPNC52FTy5y1CBywX+1/FkxIloOyQXBmZ3Zxa2AWO+lMF0JTuvqbr7G5e5CWg==
-object-inspect@^1.7.0, object-inspect@^1.8.0:
+object-inspect@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0"
integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==
object-is@^1.0.1:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.2.tgz#c5d2e87ff9e119f78b7a088441519e2eec1573b6"
- integrity sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.3.tgz#2e3b9e65560137455ee3bd62aec4d90a2ea1cc81"
+ integrity sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg==
dependencies:
define-properties "^1.1.3"
- es-abstract "^1.17.5"
+ es-abstract "^1.18.0-next.1"
object-keys@^1.0.12, object-keys@^1.1.1:
version "1.1.1"
@@ -7377,7 +7423,7 @@ object-visit@^1.0.0:
dependencies:
isobject "^3.0.0"
-object.assign@^4.1.0:
+object.assign@^4.1.0, object.assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd"
integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==
@@ -7990,9 +8036,9 @@ postcss-discard-overridden@^4.0.1:
postcss "^7.0.0"
postcss-load-config@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.0.tgz#c84d692b7bb7b41ddced94ee62e8ab31b417b003"
- integrity sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz#c5ea504f2c4aef33c7359a34de3573772ad7502a"
+ integrity sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw==
dependencies:
cosmiconfig "^5.0.0"
import-cwd "^2.0.0"
@@ -8344,6 +8390,16 @@ pretty-format@^26.4.2:
ansi-styles "^4.0.0"
react-is "^16.12.0"
+pretty-format@^26.5.2:
+ version "26.5.2"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.5.2.tgz#5d896acfdaa09210683d34b6dc0e6e21423cd3e1"
+ integrity sha512-VizyV669eqESlkOikKJI8Ryxl/kPpbdLwNdPs2GrbQs18MpySB5S0Yo0N7zkg2xTRiFq4CFw8ct5Vg4a0xP0og==
+ dependencies:
+ "@jest/types" "^26.5.2"
+ ansi-regex "^5.0.0"
+ ansi-styles "^4.0.0"
+ react-is "^16.12.0"
+
process-nextick-args@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
@@ -8665,10 +8721,10 @@ react-notification@^6.8.5:
dependencies:
prop-types "^15.6.2"
-react-overlays@^0.9.1:
- version "0.9.1"
- resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-0.9.1.tgz#d4702bfe5b5e9335b676ff5a940253771fdeed12"
- integrity sha512-b0asy/zHtRd0i2+2/uNxe3YVprF3bRT1guyr791DORjCzE/HSBMog+ul83CdtKQ1kZ+pLnxWCu5W3BMysFhHdQ==
+react-overlays@^0.9.2:
+ version "0.9.2"
+ resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-0.9.2.tgz#51ab1c62ded5af4d279bd3b943999531bbd648da"
+ integrity sha512-wOi+WqO0acnUAMCbTTW06/GRkYjHdlvIoyX4bYkLvxKrLgl2kX9WzFVyBdwukl2jvN7I7oX7ZXAz7MNOWYdgCA==
dependencies:
classnames "^2.2.5"
dom-helpers "^3.2.1"
@@ -9028,9 +9084,9 @@ regexpp@^3.1.0:
integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==
regexpu-core@^4.7.0:
- version "4.7.0"
- resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938"
- integrity sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==
+ version "4.7.1"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6"
+ integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==
dependencies:
regenerate "^1.4.0"
regenerate-unicode-properties "^8.2.0"
@@ -9358,21 +9414,21 @@ sass-lint@^1.13.1:
path-is-absolute "^1.0.0"
util "^0.10.3"
-sass-loader@^10.0.2:
- version "10.0.2"
- resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.0.2.tgz#c7b73010848b264792dd45372eea0b87cba4401e"
- integrity sha512-wV6NDUVB8/iEYMalV/+139+vl2LaRFlZGEd5/xmdcdzQcgmis+npyco6NsDTVOlNA3y2NV9Gcz+vHyFMIT+ffg==
+sass-loader@^10.0.3:
+ version "10.0.3"
+ resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.0.3.tgz#9e2f1bfdd6355f2adde4e4835d838b020bf800b0"
+ integrity sha512-W4+FV5oUdYy0PnC11ZoPrcAexODgDCa3ngxoy5X5qBhZYoPz9FPjb6Oox8Aa0ZYEyx34k8AQfOVuvqefOSAAUQ==
dependencies:
- klona "^2.0.3"
+ klona "^2.0.4"
loader-utils "^2.0.0"
neo-async "^2.6.2"
- schema-utils "^2.7.1"
+ schema-utils "^3.0.0"
semver "^7.3.2"
-sass@^1.26.10:
- version "1.26.10"
- resolved "https://registry.yarnpkg.com/sass/-/sass-1.26.10.tgz#851d126021cdc93decbf201d1eca2a20ee434760"
- integrity sha512-bzN0uvmzfsTvjz0qwccN1sPm2HxxpNI/Xa+7PlUEMS+nQvbyuEK7Y0qFqxlPHhiNHb1Ze8WQJtU31olMObkAMw==
+sass@^1.27.0:
+ version "1.27.0"
+ resolved "https://registry.yarnpkg.com/sass/-/sass-1.27.0.tgz#0657ff674206b95ec20dc638a93e179c78f6ada2"
+ integrity sha512-0gcrER56OkzotK/GGwgg4fPrKuiFlPNitO7eUJ18Bs+/NBlofJfMxmxqpqJxjae9vu0Wq8TZzrSyxZal00WDig==
dependencies:
chokidar ">=2.0.0 <4.0.0"
@@ -9405,7 +9461,7 @@ schema-utils@^1.0.0:
ajv-errors "^1.0.0"
ajv-keywords "^3.1.0"
-schema-utils@^2.2.0, schema-utils@^2.6.5, schema-utils@^2.7.0, schema-utils@^2.7.1:
+schema-utils@^2.2.0, schema-utils@^2.6.5, schema-utils@^2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7"
integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==
@@ -9414,6 +9470,15 @@ schema-utils@^2.2.0, schema-utils@^2.6.5, schema-utils@^2.7.0, schema-utils@^2.7
ajv "^6.12.4"
ajv-keywords "^3.5.2"
+schema-utils@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef"
+ integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==
+ dependencies:
+ "@types/json-schema" "^7.0.6"
+ ajv "^6.12.5"
+ ajv-keywords "^3.5.2"
+
scroll-behavior@^0.9.1:
version "0.9.12"
resolved "https://registry.yarnpkg.com/scroll-behavior/-/scroll-behavior-0.9.12.tgz#1c22d273ec4ce6cd4714a443fead50227da9424c"
@@ -9483,13 +9548,6 @@ serialize-javascript@^2.1.2:
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61"
integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==
-serialize-javascript@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
- integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==
- dependencies:
- randombytes "^2.1.0"
-
serialize-javascript@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4"
@@ -9728,7 +9786,7 @@ source-map-resolve@^0.6.0:
atob "^2.1.2"
decode-uri-component "^0.2.0"
-source-map-support@^0.5.6, source-map-support@~0.5.12:
+source-map-support@^0.5.6, source-map-support@~0.5.12, source-map-support@~0.5.19:
version "0.5.19"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
@@ -9756,7 +9814,7 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
-source-map@^0.7.3:
+source-map@^0.7.3, source-map@~0.7.2:
version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
@@ -10106,7 +10164,7 @@ strip-indent@^3.0.0:
dependencies:
min-indent "^1.0.0"
-strip-json-comments@^3.1.0:
+strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
@@ -10267,19 +10325,19 @@ terser-webpack-plugin@^1.4.3:
webpack-sources "^1.4.0"
worker-farm "^1.7.0"
-terser-webpack-plugin@^4.2.2:
- version "4.2.2"
- resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-4.2.2.tgz#d86200c700053bba637913fe4310ba1bdeb5568e"
- integrity sha512-3qAQpykRTD5DReLu5/cwpsg7EZFzP3Q0Hp2XUWJUw2mpq2jfgOKTZr8IZKKnNieRVVo1UauROTdhbQJZveGKtQ==
+terser-webpack-plugin@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz#28daef4a83bd17c1db0297070adc07fc8cfc6a9a"
+ integrity sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ==
dependencies:
cacache "^15.0.5"
find-cache-dir "^3.3.1"
- jest-worker "^26.3.0"
+ jest-worker "^26.5.0"
p-limit "^3.0.2"
- schema-utils "^2.7.1"
+ schema-utils "^3.0.0"
serialize-javascript "^5.0.1"
source-map "^0.6.1"
- terser "^5.3.2"
+ terser "^5.3.4"
webpack-sources "^1.4.3"
terser@^4.1.2:
@@ -10291,14 +10349,14 @@ terser@^4.1.2:
source-map "~0.6.1"
source-map-support "~0.5.12"
-terser@^5.3.2:
- version "5.3.2"
- resolved "https://registry.yarnpkg.com/terser/-/terser-5.3.2.tgz#f4bea90eb92945b2a028ceef79181b9bb586e7af"
- integrity sha512-H67sydwBz5jCUA32ZRL319ULu+Su1cAoZnnc+lXnenGRYWyLE3Scgkt8mNoAsMx0h5kdo758zdoS0LG9rYZXDQ==
+terser@^5.3.4:
+ version "5.3.4"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.3.4.tgz#e510e05f86e0bd87f01835c3238839193f77a60c"
+ integrity sha512-dxuB8KQo8Gt6OVOeLg/rxfcxdNZI/V1G6ze1czFUzPeCFWZRtvZMgSzlZZ5OYBZ4HoG607F6pFPNLekJyV+yVw==
dependencies:
commander "^2.20.0"
- source-map "~0.6.1"
- source-map-support "~0.5.12"
+ source-map "~0.7.2"
+ source-map-support "~0.5.19"
tesseract.js-core@^2.2.0:
version "2.2.0"
@@ -10765,20 +10823,20 @@ uuid@^3.3.2, uuid@^3.4.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
-uuid@^8.2.0, uuid@^8.3.0:
- version "8.3.0"
- resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea"
- integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==
+uuid@^8.3.0, uuid@^8.3.1:
+ version "8.3.1"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31"
+ integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==
v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745"
integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==
-v8-to-istanbul@^5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-5.0.1.tgz#0608f5b49a481458625edb058488607f25498ba5"
- integrity sha512-mbDNjuDajqYe3TXFk5qxcQy8L1msXNE37WTlLoqqpBfRsimbNcrlhQlDPntmECEcUvdC+AQ8CyMMf6EUx1r74Q==
+v8-to-istanbul@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-6.0.1.tgz#7ef0e32faa10f841fe4c1b0f8de96ed067c0be1e"
+ integrity sha512-PzM1WlqquhBvsV+Gco6WSFeg1AGdD53ccMRkFeyHRE/KRZaVacPOmQYP3EeVgDBtKD2BJ8kgynBQ5OtKiHCH+w==
dependencies:
"@types/istanbul-lib-coverage" "^2.0.1"
convert-source-map "^1.6.0"
@@ -10909,10 +10967,10 @@ webpack-assets-manifest@^3.1.1:
tapable "^1.0.0"
webpack-sources "^1.0.0"
-webpack-bundle-analyzer@^3.8.0:
- version "3.8.0"
- resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.8.0.tgz#ce6b3f908daf069fd1f7266f692cbb3bded9ba16"
- integrity sha512-PODQhAYVEourCcOuU+NiYI7WdR8QyELZGgPvB1y2tjbUpbmcQOt5Q7jEK+ttd5se0KSBKD9SXHCEozS++Wllmw==
+webpack-bundle-analyzer@^3.9.0:
+ version "3.9.0"
+ resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.9.0.tgz#f6f94db108fb574e415ad313de41a2707d33ef3c"
+ integrity sha512-Ob8amZfCm3rMB1ScjQVlbYYUEJyEjdEtQ92jqiFUYt5VkEeO2v5UMbv49P/gnmCZm3A6yaFQzCBvpZqN4MUsdA==
dependencies:
acorn "^7.1.1"
acorn-walk "^7.1.1"
@@ -10923,7 +10981,7 @@ webpack-bundle-analyzer@^3.8.0:
express "^4.16.3"
filesize "^3.6.1"
gzip-size "^5.0.0"
- lodash "^4.17.15"
+ lodash "^4.17.19"
mkdirp "^0.5.1"
opener "^1.5.1"
ws "^6.0.0"
@@ -11081,9 +11139,9 @@ whatwg-mimetype@^2.3.0:
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
whatwg-url@^8.0.0:
- version "8.2.1"
- resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.2.1.tgz#ed73417230784b281fb2a32c3c501738b46167c3"
- integrity sha512-ZmVCr6nfBeaMxEHALLEGy0LszYjpJqf6PVNQUQ1qd9Et+q7Jpygd4rGGDXgHjD8e99yLFseD69msHDM4YwPZ4A==
+ version "8.2.2"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.2.2.tgz#85e7f9795108b53d554cec640b2e8aee2a0d4bfd"
+ integrity sha512-PcVnO6NiewhkmzV0qn7A+UZ9Xx4maNTI+O+TShmfE4pqjoCMwUMjkvoNhNHPTvgR7QH9Xt3R13iHuWy2sToFxQ==
dependencies:
lodash.sortby "^4.7.0"
tr46 "^2.0.2"
@@ -11277,7 +11335,7 @@ yargs@^13.3.2:
y18n "^4.0.0"
yargs-parser "^13.1.2"
-yargs@^15.3.1:
+yargs@^15.4.1:
version "15.4.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==