Merge branch 'master' into glitch-soc/merge-upstream

This commit is contained in:
Thibaut Girka 2020-11-07 18:19:34 +01:00
commit 412218af2e
56 changed files with 403 additions and 170 deletions

View file

@ -104,7 +104,7 @@ GEM
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
blurhash (0.1.4) blurhash (0.1.4)
ffi (~> 1.10.0) ffi (~> 1.10.0)
bootsnap (1.4.8) bootsnap (1.4.9)
msgpack (~> 1.0) msgpack (~> 1.0)
brakeman (4.10.0) brakeman (4.10.0)
browser (4.2.0) browser (4.2.0)
@ -424,7 +424,7 @@ GEM
pry-rails (0.3.9) pry-rails (0.3.9)
pry (>= 0.10.4) pry (>= 0.10.4)
public_suffix (4.0.6) public_suffix (4.0.6)
puma (5.0.2) puma (5.0.4)
nio4r (~> 2.0) nio4r (~> 2.0)
pundit (2.1.0) pundit (2.1.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
@ -574,7 +574,7 @@ GEM
sidekiq (>= 3) sidekiq (>= 3)
thwait thwait
tilt (>= 1.4.0) tilt (>= 1.4.0)
sidekiq-unique-jobs (6.0.24) sidekiq-unique-jobs (6.0.25)
concurrent-ruby (~> 1.0, >= 1.0.5) concurrent-ruby (~> 1.0, >= 1.0.5)
sidekiq (>= 4.0, < 7.0) sidekiq (>= 4.0, < 7.0)
thor (>= 0.20, < 2.0) thor (>= 0.20, < 2.0)

View file

@ -53,6 +53,13 @@ module Admin
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.destroyed_msg', username: @account.acct) redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.destroyed_msg', username: @account.acct)
end end
def unsensitive
authorize @account, :unsensitive?
@account.unsensitize!
log_action :unsensitive, @account
redirect_to admin_account_path(@account.id)
end
def unsilence def unsilence
authorize @account, :unsilence? authorize @account, :unsilence?
@account.unsilence! @account.unsilence!

View file

@ -71,7 +71,7 @@ class Admin::AnnouncementsController < Admin::BaseController
private private
def set_announcements def set_announcements
@announcements = AnnouncementFilter.new(filter_params).results.page(params[:page]) @announcements = AnnouncementFilter.new(filter_params).results.reverse_chronological.page(params[:page])
end end
def set_announcement def set_announcement

View file

@ -22,6 +22,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController
active active
pending pending
disabled disabled
sensitized
silenced silenced
suspended suspended
username username
@ -68,6 +69,13 @@ class Api::V1::Admin::AccountsController < Api::BaseController
render json: @account, serializer: REST::Admin::AccountSerializer render json: @account, serializer: REST::Admin::AccountSerializer
end end
def unsensitive
authorize @account, :unsensitive?
@account.unsensitize!
log_action :unsensitive, @account
render json: @account, serializer: REST::Admin::AccountSerializer
end
def unsilence def unsilence
authorize @account, :unsilence? authorize @account, :unsilence?
@account.unsilence! @account.unsilence!

View file

@ -4,8 +4,12 @@ module StatusesHelper
EMBEDDED_CONTROLLER = 'statuses' EMBEDDED_CONTROLLER = 'statuses'
EMBEDDED_ACTION = 'embed' EMBEDDED_ACTION = 'embed'
def link_to_more(url) def link_to_newer(url)
link_to t('statuses.show_more'), url, class: 'load-more load-gap' link_to t('statuses.show_newer'), url, class: 'load-more load-gap'
end
def link_to_older(url)
link_to t('statuses.show_older'), url, class: 'load-more load-gap'
end end
def nothing_here(extra_classes = '') def nothing_here(extra_classes = '')
@ -117,6 +121,14 @@ module StatusesHelper
end end
end end
def sensitized?(status, account)
if !account.nil? && account.id == status.account_id
status.sensitive
else
status.account.sensitized? || status.sensitive
end
end
private private
def simplified_text(text) def simplified_text(text)

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import detectPassiveEvents from 'detect-passive-events'; import { supportsPassiveEvents } from 'detect-passive-events';
import { scrollTop } from '../scroll'; import { scrollTop } from '../scroll';
export default class Column extends React.PureComponent { export default class Column extends React.PureComponent {
@ -35,9 +35,9 @@ export default class Column extends React.PureComponent {
componentDidMount () { componentDidMount () {
if (this.props.bindToDocument) { if (this.props.bindToDocument) {
document.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false); document.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false);
} else { } else {
this.node.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false); this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false);
} }
} }

View file

@ -5,9 +5,9 @@ import IconButton from './icon_button';
import Overlay from 'react-overlays/lib/Overlay'; import Overlay from 'react-overlays/lib/Overlay';
import Motion from '../features/ui/util/optional_motion'; import Motion from '../features/ui/util/optional_motion';
import spring from 'react-motion/lib/spring'; import spring from 'react-motion/lib/spring';
import detectPassiveEvents from 'detect-passive-events'; import { supportsPassiveEvents } from 'detect-passive-events';
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false; const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
let id = 0; let id = 0;
class DropdownMenu extends React.PureComponent { class DropdownMenu extends React.PureComponent {

View file

@ -5,7 +5,7 @@ import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components'
import Overlay from 'react-overlays/lib/Overlay'; import Overlay from 'react-overlays/lib/Overlay';
import classNames from 'classnames'; import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import detectPassiveEvents from 'detect-passive-events'; import { supportsPassiveEvents } from 'detect-passive-events';
import { buildCustomEmojis, categoriesFromEmojis } from '../../emoji/emoji'; import { buildCustomEmojis, categoriesFromEmojis } from '../../emoji/emoji';
import { assetHost } from 'mastodon/utils/config'; import { assetHost } from 'mastodon/utils/config';
@ -29,7 +29,7 @@ const messages = defineMessages({
let EmojiPicker, Emoji; // load asynchronously let EmojiPicker, Emoji; // load asynchronously
const backgroundImageFn = () => `${assetHost}/emoji/sheet_10.png`; const backgroundImageFn = () => `${assetHost}/emoji/sheet_10.png`;
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false; const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
class ModifierPickerMenu extends React.PureComponent { class ModifierPickerMenu extends React.PureComponent {

View file

@ -5,7 +5,7 @@ import IconButton from '../../../components/icon_button';
import Overlay from 'react-overlays/lib/Overlay'; import Overlay from 'react-overlays/lib/Overlay';
import Motion from '../../ui/util/optional_motion'; import Motion from '../../ui/util/optional_motion';
import spring from 'react-motion/lib/spring'; import spring from 'react-motion/lib/spring';
import detectPassiveEvents from 'detect-passive-events'; import { supportsPassiveEvents } from 'detect-passive-events';
import classNames from 'classnames'; import classNames from 'classnames';
import Icon from 'mastodon/components/icon'; import Icon from 'mastodon/components/icon';
@ -21,7 +21,7 @@ const messages = defineMessages({
change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' }, change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
}); });
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false; const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
class PrivacyDropdownMenu extends React.PureComponent { class PrivacyDropdownMenu extends React.PureComponent {

View file

@ -31,7 +31,7 @@ import Icon from 'mastodon/components/icon';
import ComposePanel from './compose_panel'; import ComposePanel from './compose_panel';
import NavigationPanel from './navigation_panel'; import NavigationPanel from './navigation_panel';
import detectPassiveEvents from 'detect-passive-events'; import { supportsPassiveEvents } from 'detect-passive-events';
import { scrollRight } from '../../../scroll'; import { scrollRight } from '../../../scroll';
const componentMap = { const componentMap = {
@ -80,7 +80,7 @@ class ColumnsArea extends ImmutablePureComponent {
componentDidMount() { componentDidMount() {
if (!this.props.singleColumn) { if (!this.props.singleColumn) {
this.node.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false); this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false);
} }
this.lastIndex = getIndex(this.context.router.history.location.pathname); this.lastIndex = getIndex(this.context.router.history.location.pathname);
@ -97,7 +97,7 @@ class ColumnsArea extends ImmutablePureComponent {
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) { if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) {
this.node.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false); this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false);
} }
this.lastIndex = getIndex(this.context.router.history.location.pathname); this.lastIndex = getIndex(this.context.router.history.location.pathname);
this.setState({ shouldAnimate: true }); this.setState({ shouldAnimate: true });

View file

@ -113,7 +113,8 @@ class ZoomableImage extends React.PureComponent {
state = { state = {
scale: MIN_SCALE, scale: MIN_SCALE,
zoomMatrix: { zoomMatrix: {
type: null, // 'full-width' 'full-height' type: null, // 'width' 'height'
fullScreen: null, // bool
rate: null, // full screen scale rate rate: null, // full screen scale rate
clientWidth: null, clientWidth: null,
clientHeight: null, clientHeight: null,
@ -122,12 +123,15 @@ class ZoomableImage extends React.PureComponent {
clientHeightFixed: null, clientHeightFixed: null,
scrollTop: null, scrollTop: null,
scrollLeft: null, scrollLeft: null,
translateX: null,
translateY: null,
}, },
zoomState: 'expand', // 'expand' 'compress' zoomState: 'expand', // 'expand' 'compress'
navigationHidden: false, navigationHidden: false,
dragPosition: { top: 0, left: 0, x: 0, y: 0 }, dragPosition: { top: 0, left: 0, x: 0, y: 0 },
dragged: false, dragged: false,
lockScroll: { x: 0, y: 0 }, lockScroll: { x: 0, y: 0 },
lockTranslate: { x: 0, y: 0 },
} }
removers = []; removers = [];
@ -168,18 +172,24 @@ class ZoomableImage extends React.PureComponent {
} }
componentDidUpdate () { componentDidUpdate () {
this.setState({ zoomState: this.state.scale >= this.state.zoomMatrix.rate ? 'compress' : 'expand' });
if (this.state.scale === MIN_SCALE) {
this.container.style.removeProperty('cursor');
}
}
UNSAFE_componentWillReceiveProps () {
// reset when slide to next image
if (this.props.zoomButtonHidden) { if (this.props.zoomButtonHidden) {
this.setState({ scale: MIN_SCALE }, () => { this.setState({
scale: MIN_SCALE,
lockTranslate: { x: 0, y: 0 },
}, () => {
this.container.scrollLeft = 0; this.container.scrollLeft = 0;
this.container.scrollTop = 0; this.container.scrollTop = 0;
}); });
} }
this.setState({ zoomState: this.state.scale >= this.state.zoomMatrix.rate ? 'compress' : 'expand' });
if (this.state.scale === 1) {
this.container.style.removeProperty('cursor');
}
} }
removeEventListeners () { removeEventListeners () {
@ -192,7 +202,7 @@ class ZoomableImage extends React.PureComponent {
const event = normalizeWheel(e); const event = normalizeWheel(e);
if (this.state.zoomMatrix.type === 'full-width') { if (this.state.zoomMatrix.type === 'width') {
// full width, scroll vertical // full width, scroll vertical
this.container.scrollTop = Math.max(this.container.scrollTop + event.pixelY, this.state.lockScroll.y); this.container.scrollTop = Math.max(this.container.scrollTop + event.pixelY, this.state.lockScroll.y);
} else { } else {
@ -268,7 +278,7 @@ class ZoomableImage extends React.PureComponent {
} }
zoom(nextScale, midpoint) { zoom(nextScale, midpoint) {
const { scale } = this.state; const { scale, zoomMatrix } = this.state;
const { scrollLeft, scrollTop } = this.container; const { scrollLeft, scrollTop } = this.container;
// math memo: // math memo:
@ -283,6 +293,15 @@ class ZoomableImage extends React.PureComponent {
this.setState({ scale: nextScale }, () => { this.setState({ scale: nextScale }, () => {
this.container.scrollLeft = nextScrollLeft; this.container.scrollLeft = nextScrollLeft;
this.container.scrollTop = nextScrollTop; this.container.scrollTop = nextScrollTop;
// reset the translateX/Y constantly
if (nextScale < zoomMatrix.rate) {
this.setState({
lockTranslate: {
x: zoomMatrix.fullScreen ? 0 : zoomMatrix.translateX * ((nextScale - MIN_SCALE) / (zoomMatrix.rate - MIN_SCALE)),
y: zoomMatrix.fullScreen ? 0 : zoomMatrix.translateY * ((nextScale - MIN_SCALE) / (zoomMatrix.rate - MIN_SCALE)),
},
});
}
}); });
} }
@ -307,14 +326,18 @@ class ZoomableImage extends React.PureComponent {
const { offsetWidth, offsetHeight } = this.image; const { offsetWidth, offsetHeight } = this.image;
const clientHeightFixed = clientHeight - NAV_BAR_HEIGHT; const clientHeightFixed = clientHeight - NAV_BAR_HEIGHT;
const type = width/height < clientWidth / clientHeightFixed ? 'full-width' : 'full-height'; const type = width / height < clientWidth / clientHeightFixed ? 'width' : 'height';
const rate = type === 'full-width' ? clientWidth / offsetWidth : clientHeightFixed / offsetHeight; const fullScreen = type === 'width' ? width > clientWidth : height > clientHeightFixed;
const scrollTop = type === 'full-width' ? (clientHeight - offsetHeight) / 2 - NAV_BAR_HEIGHT : (clientHeightFixed - offsetHeight) / 2; const rate = type === 'width' ? Math.min(clientWidth, width) / offsetWidth : Math.min(clientHeightFixed, height) / offsetHeight;
const scrollTop = type === 'width' ? (clientHeight - offsetHeight) / 2 - NAV_BAR_HEIGHT : (clientHeightFixed - offsetHeight) / 2;
const scrollLeft = (clientWidth - offsetWidth) / 2; const scrollLeft = (clientWidth - offsetWidth) / 2;
const translateX = type === 'width' ? (width - offsetWidth) / (2 * rate) : 0;
const translateY = type === 'height' ? (height - offsetHeight) / (2 * rate) : 0;
this.setState({ this.setState({
zoomMatrix: { zoomMatrix: {
type: type, type: type,
fullScreen: fullScreen,
rate: rate, rate: rate,
clientWidth: clientWidth, clientWidth: clientWidth,
clientHeight: clientHeight, clientHeight: clientHeight,
@ -323,6 +346,8 @@ class ZoomableImage extends React.PureComponent {
clientHeightFixed: clientHeightFixed, clientHeightFixed: clientHeightFixed,
scrollTop: scrollTop, scrollTop: scrollTop,
scrollLeft: scrollLeft, scrollLeft: scrollLeft,
translateX: translateX,
translateY: translateY,
}, },
}); });
} }
@ -340,6 +365,10 @@ class ZoomableImage extends React.PureComponent {
x: 0, x: 0,
y: 0, y: 0,
}, },
lockTranslate: {
x: 0,
y: 0,
},
}, () => { }, () => {
this.container.scrollLeft = 0; this.container.scrollLeft = 0;
this.container.scrollTop = 0; this.container.scrollTop = 0;
@ -351,6 +380,10 @@ class ZoomableImage extends React.PureComponent {
x: zoomMatrix.scrollLeft, x: zoomMatrix.scrollLeft,
y: zoomMatrix.scrollTop, y: zoomMatrix.scrollTop,
}, },
lockTranslate: {
x: zoomMatrix.fullScreen ? 0 : zoomMatrix.translateX,
y: zoomMatrix.fullScreen ? 0 : zoomMatrix.translateY,
},
}, () => { }, () => {
this.container.scrollLeft = zoomMatrix.scrollLeft; this.container.scrollLeft = zoomMatrix.scrollLeft;
this.container.scrollTop = zoomMatrix.scrollTop; this.container.scrollTop = zoomMatrix.scrollTop;
@ -371,15 +404,15 @@ class ZoomableImage extends React.PureComponent {
render () { render () {
const { alt, src, width, height, intl } = this.props; const { alt, src, width, height, intl } = this.props;
const { scale } = this.state; const { scale, lockTranslate } = this.state;
const overflow = scale === 1 ? 'hidden' : 'scroll'; const overflow = scale === MIN_SCALE ? 'hidden' : 'scroll';
const zoomButtonSshouldHide = !this.state.navigationHidden && !this.props.zoomButtonHidden ? '' : 'media-modal__zoom-button--hidden'; const zoomButtonShouldHide = this.state.navigationHidden || this.props.zoomButtonHidden || this.state.zoomMatrix.rate <= MIN_SCALE ? 'media-modal__zoom-button--hidden' : '';
const zoomButtonTitle = this.state.zoomState === 'compress' ? intl.formatMessage(messages.compress) : intl.formatMessage(messages.expand); const zoomButtonTitle = this.state.zoomState === 'compress' ? intl.formatMessage(messages.compress) : intl.formatMessage(messages.expand);
return ( return (
<React.Fragment> <React.Fragment>
<IconButton <IconButton
className={`media-modal__zoom-button ${zoomButtonSshouldHide}`} className={`media-modal__zoom-button ${zoomButtonShouldHide}`}
title={zoomButtonTitle} title={zoomButtonTitle}
icon={this.state.zoomState} icon={this.state.zoomState}
onClick={this.handleZoomClick} onClick={this.handleZoomClick}
@ -402,7 +435,7 @@ class ZoomableImage extends React.PureComponent {
width={width} width={width}
height={height} height={height}
style={{ style={{
transform: `scale(${scale})`, transform: `scale(${scale}) translate(-${lockTranslate.x}px, -${lockTranslate.y}px)`,
transformOrigin: '0 0', transformOrigin: '0 0',
}} }}
draggable={false} draggable={false}

View file

@ -1,4 +1,4 @@
import detectPassiveEvents from 'detect-passive-events'; import { supportsPassiveEvents } from 'detect-passive-events';
const LAYOUT_BREAKPOINT = 630; const LAYOUT_BREAKPOINT = 630;
@ -9,7 +9,7 @@ export function isMobile(width) {
const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
let userTouching = false; let userTouching = false;
let listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false; let listenerOptions = supportsPassiveEvents ? { passive: true } : false;
function touchListener() { function touchListener() {
userTouching = true; userTouching = true;

View file

@ -111,7 +111,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
created_at: @object['published'], created_at: @object['published'],
override_timestamps: @options[:override_timestamps], override_timestamps: @options[:override_timestamps],
reply: @object['inReplyTo'].present?, reply: @object['inReplyTo'].present?,
sensitive: @object['sensitive'] || false, sensitive: @account.sensitized? || @object['sensitive'] || false,
visibility: visibility_from_audience, visibility: visibility_from_audience,
thread: replied_to_status, thread: replied_to_status,
conversation: conversation_from_uri(@object['conversation']), conversation: conversation_from_uri(@object['conversation']),

View file

@ -50,6 +50,7 @@
# avatar_storage_schema_version :integer # avatar_storage_schema_version :integer
# header_storage_schema_version :integer # header_storage_schema_version :integer
# devices_url :string # devices_url :string
# sensitized_at :datetime
# #
class Account < ApplicationRecord class Account < ApplicationRecord
@ -96,6 +97,7 @@ class Account < ApplicationRecord
scope :partitioned, -> { order(Arel.sql('row_number() over (partition by domain)')) } scope :partitioned, -> { order(Arel.sql('row_number() over (partition by domain)')) }
scope :silenced, -> { where.not(silenced_at: nil) } scope :silenced, -> { where.not(silenced_at: nil) }
scope :suspended, -> { where.not(suspended_at: nil) } scope :suspended, -> { where.not(suspended_at: nil) }
scope :sensitized, -> { where.not(sensitized_at: nil) }
scope :without_suspended, -> { where(suspended_at: nil) } scope :without_suspended, -> { where(suspended_at: nil) }
scope :without_silenced, -> { where(silenced_at: nil) } scope :without_silenced, -> { where(silenced_at: nil) }
scope :recent, -> { reorder(id: :desc) } scope :recent, -> { reorder(id: :desc) }
@ -238,6 +240,18 @@ class Account < ApplicationRecord
end end
end end
def sensitized?
sensitized_at.present?
end
def sensitize!(date = Time.now.utc)
update!(sensitized_at: date)
end
def unsensitize!
update!(sensitized_at: nil)
end
def memorialize! def memorialize!
update!(memorial: true) update!(memorial: true)
end end

View file

@ -13,7 +13,7 @@
# #
class AccountWarning < ApplicationRecord class AccountWarning < ApplicationRecord
enum action: %i(none disable silence suspend), _suffix: :action enum action: %i(none disable sensitive silence suspend), _suffix: :action
belongs_to :account, inverse_of: :account_warnings belongs_to :account, inverse_of: :account_warnings
belongs_to :target_account, class_name: 'Account', inverse_of: :targeted_account_warnings belongs_to :target_account, class_name: 'Account', inverse_of: :targeted_account_warnings

View file

@ -8,6 +8,7 @@ class Admin::AccountAction
TYPES = %w( TYPES = %w(
none none
disable disable
sensitive
silence silence
suspend suspend
).freeze ).freeze
@ -64,6 +65,8 @@ class Admin::AccountAction
case type case type
when 'disable' when 'disable'
handle_disable! handle_disable!
when 'sensitive'
handle_sensitive!
when 'silence' when 'silence'
handle_silence! handle_silence!
when 'suspend' when 'suspend'
@ -109,6 +112,12 @@ class Admin::AccountAction
target_account.user&.disable! target_account.user&.disable!
end end
def handle_sensitive!
authorize(target_account, :sensitive?)
log_action(:sensitive, target_account)
target_account.sensitize!
end
def handle_silence! def handle_silence!
authorize(target_account, :silence?) authorize(target_account, :silence?)
log_action(:silence, target_account) log_action(:silence, target_account)

View file

@ -35,9 +35,11 @@ class Admin::ActionLogFilter
reopen_report: { target_type: 'Report', action: 'reopen' }.freeze, reopen_report: { target_type: 'Report', action: 'reopen' }.freeze,
reset_password_user: { target_type: 'User', action: 'reset_password' }.freeze, reset_password_user: { target_type: 'User', action: 'reset_password' }.freeze,
resolve_report: { target_type: 'Report', action: 'resolve' }.freeze, resolve_report: { target_type: 'Report', action: 'resolve' }.freeze,
sensitive_account: { target_type: 'Account', action: 'sensitive' }.freeze,
silence_account: { target_type: 'Account', action: 'silence' }.freeze, silence_account: { target_type: 'Account', action: 'silence' }.freeze,
suspend_account: { target_type: 'Account', action: 'suspend' }.freeze, suspend_account: { target_type: 'Account', action: 'suspend' }.freeze,
unassigned_report: { target_type: 'Report', action: 'unassigned' }.freeze, unassigned_report: { target_type: 'Report', action: 'unassigned' }.freeze,
unsensitive_account: { target_type: 'Account', action: 'unsensitive' }.freeze,
unsilence_account: { target_type: 'Account', action: 'unsilence' }.freeze, unsilence_account: { target_type: 'Account', action: 'unsilence' }.freeze,
unsuspend_account: { target_type: 'Account', action: 'unsuspend' }.freeze, unsuspend_account: { target_type: 'Account', action: 'unsuspend' }.freeze,
update_announcement: { target_type: 'Announcement', action: 'update' }.freeze, update_announcement: { target_type: 'Announcement', action: 'update' }.freeze,

View file

@ -22,6 +22,7 @@ class Announcement < ApplicationRecord
scope :published, -> { where(published: true) } scope :published, -> { where(published: true) }
scope :without_muted, ->(account) { joins("LEFT OUTER JOIN announcement_mutes ON announcement_mutes.announcement_id = announcements.id AND announcement_mutes.account_id = #{account.id}").where('announcement_mutes.id IS NULL') } scope :without_muted, ->(account) { joins("LEFT OUTER JOIN announcement_mutes ON announcement_mutes.announcement_id = announcements.id AND announcement_mutes.account_id = #{account.id}").where('announcement_mutes.id IS NULL') }
scope :chronological, -> { order(Arel.sql('COALESCE(announcements.starts_at, announcements.scheduled_at, announcements.published_at, announcements.created_at) ASC')) } scope :chronological, -> { order(Arel.sql('COALESCE(announcements.starts_at, announcements.scheduled_at, announcements.published_at, announcements.created_at) ASC')) }
scope :reverse_chronological, -> { order(Arel.sql('COALESCE(announcements.starts_at, announcements.scheduled_at, announcements.published_at, announcements.created_at) DESC')) }
has_many :announcement_mutes, dependent: :destroy has_many :announcement_mutes, dependent: :destroy
has_many :announcement_reactions, dependent: :destroy has_many :announcement_reactions, dependent: :destroy

View file

@ -25,6 +25,14 @@ class AccountPolicy < ApplicationPolicy
staff? staff?
end end
def sensitive?
staff? && !record.user&.staff?
end
def unsensitive?
staff?
end
def silence? def silence?
staff? && !record.user&.staff? staff? && !record.user&.staff?
end end

View file

@ -110,6 +110,10 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
ActivityPub::TagManager.instance.cc(object) ActivityPub::TagManager.instance.cc(object)
end end
def sensitive
object.account.sensitized? || object.sensitive
end
def virtual_tags def virtual_tags
object.active_mentions.to_a.sort_by(&:id) + object.tags + object.emojis object.active_mentions.to_a.sort_by(&:id) + object.tags + object.emojis
end end

View file

@ -60,6 +60,14 @@ class REST::StatusSerializer < ActiveModel::Serializer
end end
end end
def sensitive
if current_user? && current_user.account_id == object.account_id
object.sensitive
else
object.account.sensitized? || object.sensitive
end
end
def uri def uri
ActivityPub::TagManager.instance.uri_for(object) ActivityPub::TagManager.instance.uri_for(object)
end end

View file

@ -18,7 +18,7 @@ class SuspendAccountService < BaseService
def unmerge_from_home_timelines! def unmerge_from_home_timelines!
@account.followers_for_local_distribution.find_each do |follower| @account.followers_for_local_distribution.find_each do |follower|
FeedManager.instance.unmerge_from_timeline(@account, follower) FeedManager.instance.unmerge_from_home(@account, follower)
end end
end end
@ -39,11 +39,15 @@ class SuspendAccountService < BaseService
styles.each do |style| styles.each do |style|
case Paperclip::Attachment.default_options[:storage] case Paperclip::Attachment.default_options[:storage]
when :s3 when :s3
attachment.s3_object(style).acl.put(:private) attachment.s3_object(style).acl.put(acl: 'private')
when :fog when :fog
# Not supported # Not supported
when :filesystem when :filesystem
FileUtils.chmod(0o600 & ~File.umask, attachment.path(style)) begin
FileUtils.chmod(0o600 & ~File.umask, attachment.path(style)) unless attachment.path(style).nil?
rescue Errno::ENOENT
Rails.logger.warn "Tried to change permission on non-existent file #{attachment.path(style)}"
end
end end
end end
end end

View file

@ -18,7 +18,7 @@ class UnsuspendAccountService < BaseService
def merge_into_home_timelines! def merge_into_home_timelines!
@account.followers_for_local_distribution.find_each do |follower| @account.followers_for_local_distribution.find_each do |follower|
FeedManager.instance.merge_into_timeline(@account, follower) FeedManager.instance.merge_into_home(@account, follower)
end end
end end
@ -39,11 +39,15 @@ class UnsuspendAccountService < BaseService
styles.each do |style| styles.each do |style|
case Paperclip::Attachment.default_options[:storage] case Paperclip::Attachment.default_options[:storage]
when :s3 when :s3
attachment.s3_object(style).acl.put(Paperclip::Attachment.default_options[:s3_permissions]) attachment.s3_object(style).acl.put(acl: Paperclip::Attachment.default_options[:s3_permissions])
when :fog when :fog
# Not supported # Not supported
when :filesystem when :filesystem
FileUtils.chmod(0o666 & ~File.umask, attachment.path(style)) begin
FileUtils.chmod(0o666 & ~File.umask, attachment.path(style)) unless attachment.path(style).nil?
rescue Errno::ENOENT
Rails.logger.warn "Tried to change permission on non-existent file #{attachment.path(style)}"
end
end end
end end
end end

View file

@ -39,12 +39,12 @@
= render partial: 'statuses/status', collection: @pinned_statuses, as: :status, locals: { pinned: true } = render partial: 'statuses/status', collection: @pinned_statuses, as: :status, locals: { pinned: true }
- if @newer_url - if @newer_url
.entry= link_to_more @newer_url .entry= link_to_newer @newer_url
= render partial: 'statuses/status', collection: @statuses, as: :status = render partial: 'statuses/status', collection: @statuses, as: :status
- if @older_url - if @older_url
.entry= link_to_more @older_url .entry= link_to_older @older_url
.column-1 .column-1
- if @account.memorial? - if @account.memorial?

View file

@ -69,6 +69,8 @@
= t('admin.accounts.confirming') = t('admin.accounts.confirming')
- elsif @account.local? && !@account.user_approved? - elsif @account.local? && !@account.user_approved?
= t('admin.accounts.pending') = t('admin.accounts.pending')
- elsif @account.sensitized?
= t('admin.accounts.sensitive')
- else - else
= t('admin.accounts.no_limits_imposed') = t('admin.accounts.no_limits_imposed')
.dashboard__counters__label= t 'admin.accounts.login_status' .dashboard__counters__label= t 'admin.accounts.login_status'
@ -192,6 +194,11 @@
- else - else
= link_to t('admin.accounts.disable'), new_admin_account_action_path(@account.id, type: 'disable'), class: 'button' if can?(:disable, @account.user) = link_to t('admin.accounts.disable'), new_admin_account_action_path(@account.id, type: 'disable'), class: 'button' if can?(:disable, @account.user)
- if @account.sensitized?
= link_to t('admin.accounts.undo_sensitized'), unsensitive_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsensitive, @account)
- elsif !@account.local? || @account.user_approved?
= link_to t('admin.accounts.sensitive'), new_admin_account_action_path(@account.id, type: 'sensitive'), class: 'button' if can?(:sensitive, @account)
- if @account.silenced? - if @account.silenced?
= link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsilence, @account) = link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsilence, @account)
- elsif !@account.local? || @account.user_approved? - elsif !@account.local? || @account.user_approved?

View file

@ -1,8 +1,8 @@
- content_for :header_tags do - content_for :header_tags do
= preload_link_tag asset_pack_path('features/getting_started.js'), crossorigin: 'anonymous' = preload_pack_asset 'features/getting_started.js', crossorigin: 'anonymous'
= preload_link_tag asset_pack_path('features/compose.js'), crossorigin: 'anonymous' = preload_pack_asset 'features/compose.js', crossorigin: 'anonymous'
= preload_link_tag asset_pack_path('features/home_timeline.js'), crossorigin: 'anonymous' = preload_pack_asset 'features/home_timeline.js', crossorigin: 'anonymous'
= preload_link_tag asset_pack_path('features/notifications.js'), crossorigin: 'anonymous' = preload_pack_asset 'features/notifications.js', crossorigin: 'anonymous'
%meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key} %meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key}
= render_initial_state = render_initial_state

View file

@ -2,12 +2,12 @@
- if theme[:pack] != 'common' && theme[:common] - if theme[:pack] != 'common' && theme[:common]
= render partial: 'layouts/theme', object: theme[:common] = render partial: 'layouts/theme', object: theme[:common]
- if theme[:pack] - if theme[:pack]
= javascript_pack_tag theme[:flavour] ? "flavours/#{theme[:flavour]}/#{theme[:pack]}" : "core/#{theme[:pack]}", integrity: true, crossorigin: 'anonymous' = javascript_pack_tag theme[:flavour] ? "flavours/#{theme[:flavour]}/#{theme[:pack]}" : "core/#{theme[:pack]}", crossorigin: 'anonymous'
- if theme[:skin] - if theme[:skin]
- if !theme[:flavour] || theme[:skin] == 'default' - if !theme[:flavour] || theme[:skin] == 'default'
= stylesheet_pack_tag theme[:flavour] ? "flavours/#{theme[:flavour]}/#{theme[:pack]}" : "core/#{theme[:pack]}", integrity: true, media: 'all' = stylesheet_pack_tag theme[:flavour] ? "flavours/#{theme[:flavour]}/#{theme[:pack]}" : "core/#{theme[:pack]}", media: 'all', crossorigin: 'anonymous'
- else - else
= stylesheet_pack_tag "skins/#{theme[:flavour]}/#{theme[:skin]}/#{theme[:pack]}" = stylesheet_pack_tag "skins/#{theme[:flavour]}/#{theme[:skin]}/#{theme[:pack]}", crossorigin: 'anonymous'
- if theme[:preload] - if theme[:preload]
- theme[:preload].each do |link| - theme[:preload].each do |link|
%link{ href: asset_pack_path("#{link}.js"), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ %link{ href: asset_pack_path("#{link}.js"), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/

View file

@ -21,12 +21,12 @@
%title= content_for?(:page_title) ? safe_join([yield(:page_title).chomp.html_safe, title], ' - ') : title %title= content_for?(:page_title) ? safe_join([yield(:page_title).chomp.html_safe, title], ' - ') : title
= javascript_pack_tag "locales", integrity: true, crossorigin: 'anonymous' = javascript_pack_tag "locales", crossorigin: 'anonymous'
- if @theme - if @theme
- if @theme[:supported_locales].include? I18n.locale.to_s - if @theme[:supported_locales].include? I18n.locale.to_s
= javascript_pack_tag "locales/#{@theme[:flavour]}/#{I18n.locale}", integrity: true, crossorigin: 'anonymous' = javascript_pack_tag "locales/#{@theme[:flavour]}/#{I18n.locale}", crossorigin: 'anonymous'
- elsif @theme[:supported_locales].include? 'en' - elsif @theme[:supported_locales].include? 'en'
= javascript_pack_tag "locales/#{@theme[:flavour]}/en", integrity: true, crossorigin: 'anonymous' = javascript_pack_tag "locales/#{@theme[:flavour]}/en", crossorigin: 'anonymous'
= csrf_meta_tags = csrf_meta_tags
%meta{ name: 'style-nonce', content: request.content_security_policy_nonce } %meta{ name: 'style-nonce', content: request.content_security_policy_nonce }

View file

@ -12,12 +12,12 @@
%link{ rel: 'dns-prefetch', href: storage_host }/ %link{ rel: 'dns-prefetch', href: storage_host }/
= render_initial_state = render_initial_state
= javascript_pack_tag "locales", integrity: true, crossorigin: 'anonymous' = javascript_pack_tag "locales", crossorigin: 'anonymous'
- if @theme - if @theme
- if @theme[:supported_locales].include? I18n.locale.to_s - if @theme[:supported_locales].include? I18n.locale.to_s
= javascript_pack_tag "locales/#{@theme[:flavour]}/#{I18n.locale}", integrity: true, crossorigin: 'anonymous' = javascript_pack_tag "locales/#{@theme[:flavour]}/#{I18n.locale}", crossorigin: 'anonymous'
- elsif @theme[:supported_locales].include? 'en' - elsif @theme[:supported_locales].include? 'en'
= javascript_pack_tag "locales/#{@theme[:flavour]}/en", integrity: true, crossorigin: 'anonymous' = javascript_pack_tag "locales/#{@theme[:flavour]}/en", crossorigin: 'anonymous'
= render partial: 'layouts/theme', object: @core = render partial: 'layouts/theme', object: @core
= render partial: 'layouts/theme', object: @theme = render partial: 'layouts/theme', object: @theme

View file

@ -5,7 +5,7 @@
%meta{ charset: 'utf-8' }/ %meta{ charset: 'utf-8' }/
%title= safe_join([yield(:page_title), Setting.default_settings['site_title']], ' - ') %title= safe_join([yield(:page_title), Setting.default_settings['site_title']], ' - ')
%meta{ content: 'width=device-width,initial-scale=1', name: 'viewport' }/ %meta{ content: 'width=device-width,initial-scale=1', name: 'viewport' }/
= javascript_pack_tag "locales", integrity: true, crossorigin: 'anonymous' = javascript_pack_tag "locales", crossorigin: 'anonymous'
= render partial: 'layouts/theme', object: (@core || { pack: 'common' }) = render partial: 'layouts/theme', object: (@core || { pack: 'common' })
= render partial: 'layouts/theme', object: (@theme || { pack: 'error', flavour: 'glitch', common: { pack: 'common', flavour: 'glitch', skin: 'default' } }) = render partial: 'layouts/theme', object: (@theme || { pack: 'error', flavour: 'glitch', common: { pack: 'common', flavour: 'glitch', skin: 'default' } })
%body.error %body.error

View file

@ -1,11 +1,11 @@
- content_for :header_tags do - content_for :header_tags do
= render_initial_state = render_initial_state
= javascript_pack_tag "locales", integrity: true, crossorigin: 'anonymous' = javascript_pack_tag "locales", crossorigin: 'anonymous'
- if @theme - if @theme
- if @theme[:supported_locales].include? I18n.locale.to_s - if @theme[:supported_locales].include? I18n.locale.to_s
= javascript_pack_tag "locales/#{@theme[:flavour]}/#{I18n.locale}", integrity: true, crossorigin: 'anonymous' = javascript_pack_tag "locales/#{@theme[:flavour]}/#{I18n.locale}", crossorigin: 'anonymous'
- elsif @theme[:supported_locales].include? 'en' - elsif @theme[:supported_locales].include? 'en'
= javascript_pack_tag "locales/#{@theme[:flavour]}/en", integrity: true, crossorigin: 'anonymous' = javascript_pack_tag "locales/#{@theme[:flavour]}/en", crossorigin: 'anonymous'
= render partial: 'layouts/theme', object: @core = render partial: 'layouts/theme', object: @core
= render partial: 'layouts/theme', object: @theme = render partial: 'layouts/theme', object: @theme

View file

@ -29,17 +29,17 @@
- if !status.media_attachments.empty? - if !status.media_attachments.empty?
- if status.media_attachments.first.video? - if status.media_attachments.first.video?
- video = status.media_attachments.first - video = status.media_attachments.first
= react_component :video, src: full_asset_url(video.file.url(:original)), preview: full_asset_url(video.thumbnail.present? ? video.thumbnail.url : video.file.url(:small)), blurhash: video.blurhash, sensitive: status.sensitive?, width: 670, height: 380, detailed: true, inline: true, alt: video.description do = react_component :video, src: full_asset_url(video.file.url(:original)), preview: full_asset_url(video.thumbnail.present? ? video.thumbnail.url : video.file.url(:small)), blurhash: video.blurhash, sensitive: sensitized?(status, current_account), width: 670, height: 380, detailed: true, inline: true, alt: video.description do
= render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments } = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
- elsif status.media_attachments.first.audio? - elsif status.media_attachments.first.audio?
- audio = status.media_attachments.first - audio = status.media_attachments.first
= react_component :audio, src: full_asset_url(audio.file.url(:original)), poster: full_asset_url(audio.thumbnail.present? ? audio.thumbnail.url : status.account.avatar_static_url), backgroundColor: audio.file.meta.dig('colors', 'background'), foregroundColor: audio.file.meta.dig('colors', 'foreground'), accentColor: audio.file.meta.dig('colors', 'accent'), width: 670, height: 380, alt: audio.description, duration: audio.file.meta.dig('original', 'duration') do = react_component :audio, src: full_asset_url(audio.file.url(:original)), poster: full_asset_url(audio.thumbnail.present? ? audio.thumbnail.url : status.account.avatar_static_url), backgroundColor: audio.file.meta.dig('colors', 'background'), foregroundColor: audio.file.meta.dig('colors', 'foreground'), accentColor: audio.file.meta.dig('colors', 'accent'), width: 670, height: 380, alt: audio.description, duration: audio.file.meta.dig('original', 'duration') do
= render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments } = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
- else - else
= react_component :media_gallery, height: 380, sensitive: status.sensitive?, standalone: true, autoplay: autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do = react_component :media_gallery, height: 380, sensitive: sensitized?(status, current_account), standalone: true, autoplay: autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
= render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments } = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
- elsif status.preview_card - elsif status.preview_card
= react_component :card, sensitive: status.sensitive?, 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json = react_component :card, sensitive: sensitized?(status, current_account), 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json
.detailed-status__meta .detailed-status__meta
%data.dt-published{ value: status.created_at.to_time.iso8601 } %data.dt-published{ value: status.created_at.to_time.iso8601 }

View file

@ -35,17 +35,17 @@
- if !status.media_attachments.empty? - if !status.media_attachments.empty?
- if status.media_attachments.first.video? - if status.media_attachments.first.video?
- video = status.media_attachments.first - video = status.media_attachments.first
= react_component :video, src: full_asset_url(video.file.url(:original)), preview: full_asset_url(video.thumbnail.present? ? video.thumbnail.url : video.file.url(:small)), blurhash: video.blurhash, sensitive: status.sensitive?, width: 610, height: 343, inline: true, alt: video.description do = react_component :video, src: full_asset_url(video.file.url(:original)), preview: full_asset_url(video.thumbnail.present? ? video.thumbnail.url : video.file.url(:small)), blurhash: video.blurhash, sensitive: sensitized?(status, current_account), width: 610, height: 343, inline: true, alt: video.description do
= render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments } = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
- elsif status.media_attachments.first.audio? - elsif status.media_attachments.first.audio?
- audio = status.media_attachments.first - audio = status.media_attachments.first
= react_component :audio, src: full_asset_url(audio.file.url(:original)), poster: full_asset_url(audio.thumbnail.present? ? audio.thumbnail.url : status.account.avatar_static_url), backgroundColor: audio.file.meta.dig('colors', 'background'), foregroundColor: audio.file.meta.dig('colors', 'foreground'), accentColor: audio.file.meta.dig('colors', 'accent'), width: 610, height: 343, alt: audio.description, duration: audio.file.meta.dig('original', 'duration') do = react_component :audio, src: full_asset_url(audio.file.url(:original)), poster: full_asset_url(audio.thumbnail.present? ? audio.thumbnail.url : status.account.avatar_static_url), backgroundColor: audio.file.meta.dig('colors', 'background'), foregroundColor: audio.file.meta.dig('colors', 'foreground'), accentColor: audio.file.meta.dig('colors', 'accent'), width: 610, height: 343, alt: audio.description, duration: audio.file.meta.dig('original', 'duration') do
= render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments } = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
- else - else
= react_component :media_gallery, height: 343, sensitive: status.sensitive?, autoplay: autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do = react_component :media_gallery, height: 343, sensitive: sensitized?(status, current_account), autoplay: autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
= render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments } = render partial: 'statuses/attachment_list', locals: { attachments: status.media_attachments }
- elsif status.preview_card - elsif status.preview_card
= react_component :card, sensitive: status.sensitive?, 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json = react_component :card, sensitive: sensitized?(status, current_account), 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json
- if !status.in_reply_to_id.nil? && status.in_reply_to_account_id == status.account.id - if !status.in_reply_to_id.nil? && status.in_reply_to_account_id == status.account.id
= link_to ActivityPub::TagManager.instance.url_for(status), class: 'status__content__read-more-button', target: stream_link_target, rel: 'noopener noreferrer' do = link_to ActivityPub::TagManager.instance.url_for(status), class: 'status__content__read-more-button', target: stream_link_target, rel: 'noopener noreferrer' do

View file

@ -17,7 +17,7 @@
- if status.reply? && include_threads - if status.reply? && include_threads
- if @next_ancestor - if @next_ancestor
.entry{ class: entry_classes } .entry{ class: entry_classes }
= link_to_more ActivityPub::TagManager.instance.url_for(@next_ancestor) = link_to_older ActivityPub::TagManager.instance.url_for(@next_ancestor)
= render partial: 'statuses/status', collection: @ancestors, as: :status, locals: { is_predecessor: true, direct_reply_id: status.in_reply_to_id }, autoplay: autoplay = render partial: 'statuses/status', collection: @ancestors, as: :status, locals: { is_predecessor: true, direct_reply_id: status.in_reply_to_id }, autoplay: autoplay
@ -44,16 +44,16 @@
- if include_threads - if include_threads
- if @since_descendant_thread_id - if @since_descendant_thread_id
.entry{ class: entry_classes } .entry{ class: entry_classes }
= link_to_more short_account_status_url(status.account.username, status, max_descendant_thread_id: @since_descendant_thread_id + 1) = link_to_newer short_account_status_url(status.account.username, status, max_descendant_thread_id: @since_descendant_thread_id + 1)
- @descendant_threads.each do |thread| - @descendant_threads.each do |thread|
= render partial: 'statuses/status', collection: thread[:statuses], as: :status, locals: { is_successor: true, parent_id: status.id }, autoplay: autoplay = render partial: 'statuses/status', collection: thread[:statuses], as: :status, locals: { is_successor: true, parent_id: status.id }, autoplay: autoplay
- if thread[:next_status] - if thread[:next_status]
.entry{ class: entry_classes } .entry{ class: entry_classes }
= link_to_more ActivityPub::TagManager.instance.url_for(thread[:next_status]) = link_to_newer ActivityPub::TagManager.instance.url_for(thread[:next_status])
- if @next_descendant_thread - if @next_descendant_thread
.entry{ class: entry_classes } .entry{ class: entry_classes }
= link_to_more short_account_status_url(status.account.username, status, since_descendant_thread_id: @max_descendant_thread_id - 1) = link_to_newer short_account_status_url(status.account.username, status, since_descendant_thread_id: @max_descendant_thread_id - 1)
- if include_threads && !embedded_view? && !user_signed_in? - if include_threads && !embedded_view? && !user_signed_in?
.entry{ class: entry_classes } .entry{ class: entry_classes }

View file

@ -22,6 +22,8 @@ require_relative '../lib/mastodon/version'
require_relative '../lib/devise/two_factor_ldap_authenticatable' require_relative '../lib/devise/two_factor_ldap_authenticatable'
require_relative '../lib/devise/two_factor_pam_authenticatable' require_relative '../lib/devise/two_factor_pam_authenticatable'
require_relative '../lib/chewy/strategy/custom_sidekiq' require_relative '../lib/chewy/strategy/custom_sidekiq'
require_relative '../lib/webpacker/manifest_extensions'
require_relative '../lib/webpacker/helper_extensions'
Dotenv::Railtie.load Dotenv::Railtie.load

View file

@ -10,6 +10,7 @@ Warden::Manager.after_set_user except: :fetch do |user, warden|
expires: 1.year.from_now, expires: 1.year.from_now,
httponly: true, httponly: true,
secure: (Rails.env.production? || ENV['LOCAL_HTTPS'] == 'true'), secure: (Rails.env.production? || ENV['LOCAL_HTTPS'] == 'true'),
same_site: :lax,
} }
end end
@ -20,6 +21,7 @@ Warden::Manager.after_fetch do |user, warden|
expires: 1.year.from_now, expires: 1.year.from_now,
httponly: true, httponly: true,
secure: (Rails.env.production? || ENV['LOCAL_HTTPS'] == 'true'), secure: (Rails.env.production? || ENV['LOCAL_HTTPS'] == 'true'),
same_site: :lax,
} }
else else
warden.logout warden.logout

View file

@ -0,0 +1,2 @@
Makara::Cookie::DEFAULT_OPTIONS[:same_site] = :lax
Makara::Cookie::DEFAULT_OPTIONS[:secure] = Rails.env.production? || ENV['LOCAL_HTTPS'] == 'true'

View file

@ -1,3 +1,7 @@
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
Rails.application.config.session_store :cookie_store, key: '_mastodon_session', secure: (Rails.env.production? || ENV['LOCAL_HTTPS'] == 'true') Rails.application.config.session_store :cookie_store, {
key: '_mastodon_session',
secure: (Rails.env.production? || ENV['LOCAL_HTTPS'] == 'true'),
same_site: :lax,
}

View file

@ -188,6 +188,8 @@ en:
search: Search search: Search
search_same_email_domain: Other users with the same e-mail domain search_same_email_domain: Other users with the same e-mail domain
search_same_ip: Other users with the same IP search_same_ip: Other users with the same IP
sensitive: Sensitive
sensitized: marked as sensitive
shared_inbox_url: Shared inbox URL shared_inbox_url: Shared inbox URL
show: show:
created_reports: Made reports created_reports: Made reports
@ -202,6 +204,7 @@ en:
time_in_queue: Waiting in queue %{time} time_in_queue: Waiting in queue %{time}
title: Accounts title: Accounts
unconfirmed_email: Unconfirmed email unconfirmed_email: Unconfirmed email
undo_sensitized: Undo sensitive
undo_silenced: Undo silence undo_silenced: Undo silence
undo_suspension: Undo suspension undo_suspension: Undo suspension
unsilenced_msg: Successfully unlimited %{username}'s account unsilenced_msg: Successfully unlimited %{username}'s account
@ -243,9 +246,11 @@ en:
reopen_report: Reopen Report reopen_report: Reopen Report
reset_password_user: Reset Password reset_password_user: Reset Password
resolve_report: Resolve Report resolve_report: Resolve Report
sensitive_account: Mark the media in your account as sensitive
silence_account: Silence Account silence_account: Silence Account
suspend_account: Suspend Account suspend_account: Suspend Account
unassigned_report: Unassign Report unassigned_report: Unassign Report
unsensitive_account: Unmark the media in your account as sensitive
unsilence_account: Unsilence Account unsilence_account: Unsilence Account
unsuspend_account: Unsuspend Account unsuspend_account: Unsuspend Account
update_announcement: Update Announcement update_announcement: Update Announcement
@ -281,9 +286,11 @@ en:
reopen_report: "%{name} reopened report %{target}" reopen_report: "%{name} reopened report %{target}"
reset_password_user: "%{name} reset password of user %{target}" reset_password_user: "%{name} reset password of user %{target}"
resolve_report: "%{name} resolved report %{target}" resolve_report: "%{name} resolved report %{target}"
sensitive_account: "%{name} marked %{target}'s media as sensitive"
silence_account: "%{name} silenced %{target}'s account" silence_account: "%{name} silenced %{target}'s account"
suspend_account: "%{name} suspended %{target}'s account" suspend_account: "%{name} suspended %{target}'s account"
unassigned_report: "%{name} unassigned report %{target}" unassigned_report: "%{name} unassigned report %{target}"
unsensitive_account: "%{name} unmarked %{target}'s media as sensitive"
unsilence_account: "%{name} unsilenced %{target}'s account" unsilence_account: "%{name} unsilenced %{target}'s account"
unsuspend_account: "%{name} unsuspended %{target}'s account" unsuspend_account: "%{name} unsuspended %{target}'s account"
update_announcement: "%{name} updated announcement %{target}" update_announcement: "%{name} updated announcement %{target}"
@ -1203,6 +1210,8 @@ en:
other: "%{count} votes" other: "%{count} votes"
vote: Vote vote: Vote
show_more: Show more show_more: Show more
show_newer: Show newer
show_older: Show older
show_thread: Show thread show_thread: Show thread
sign_in_to_participate: Sign in to participate in the conversation sign_in_to_participate: Sign in to participate in the conversation
title: '%{name}: "%{quote}"' title: '%{name}: "%{quote}"'
@ -1339,6 +1348,7 @@ en:
warning: warning:
explanation: explanation:
disable: You can no longer login to your account or use it in any other way, but your profile and other data remains intact. disable: You can no longer login to your account or use it in any other way, but your profile and other data remains intact.
sensitive: Your uploaded media files and linked media will be treated as sensitive.
silence: You can still use your account but only people who are already following you will see your toots on this server, and you may be excluded from various public listings. However, others may still manually follow you. silence: You can still use your account but only people who are already following you will see your toots on this server, and you may be excluded from various public listings. However, others may still manually follow you.
suspend: You can no longer use your account, and your profile and other data are no longer accessible. You can still login to request a backup of your data until the data is fully removed, but we will retain some data to prevent you from evading the suspension. suspend: You can no longer use your account, and your profile and other data are no longer accessible. You can still login to request a backup of your data until the data is fully removed, but we will retain some data to prevent you from evading the suspension.
get_in_touch: You can reply to this e-mail to get in touch with the staff of %{instance}. get_in_touch: You can reply to this e-mail to get in touch with the staff of %{instance}.
@ -1347,11 +1357,13 @@ en:
subject: subject:
disable: Your account %{acct} has been frozen disable: Your account %{acct} has been frozen
none: Warning for %{acct} none: Warning for %{acct}
sensitive: Your account %{acct} posting media has been marked as sensitive
silence: Your account %{acct} has been limited silence: Your account %{acct} has been limited
suspend: Your account %{acct} has been suspended suspend: Your account %{acct} has been suspended
title: title:
disable: Account frozen disable: Account frozen
none: Warning none: Warning
sensitive: Your media has been marked as sensitive
silence: Account limited silence: Account limited
suspend: Account suspended suspend: Account suspended
welcome: welcome:

View file

@ -172,6 +172,8 @@ ja:
search: 検索 search: 検索
search_same_email_domain: 同じドメインのメールアドレスを使用しているユーザー search_same_email_domain: 同じドメインのメールアドレスを使用しているユーザー
search_same_ip: 同じ IP のユーザーを検索 search_same_ip: 同じ IP のユーザーを検索
sensitive: 閲覧注意
sensitized: 閲覧注意済み
shared_inbox_url: Shared inbox URL shared_inbox_url: Shared inbox URL
show: show:
created_reports: このアカウントで作られた通報 created_reports: このアカウントで作られた通報
@ -184,6 +186,7 @@ ja:
time_in_queue: "%{time} 待ち" time_in_queue: "%{time} 待ち"
title: アカウント title: アカウント
unconfirmed_email: 確認待ちのメールアドレス unconfirmed_email: 確認待ちのメールアドレス
undo_sensitized: 閲覧注意から戻す
undo_silenced: サイレンスから戻す undo_silenced: サイレンスから戻す
undo_suspension: 停止から戻す undo_suspension: 停止から戻す
unsubscribe: 購読の解除 unsubscribe: 購読の解除
@ -220,9 +223,11 @@ ja:
reopen_report: 通報を再度開く reopen_report: 通報を再度開く
reset_password_user: パスワードをリセット reset_password_user: パスワードをリセット
resolve_report: 通報を解決済みにする resolve_report: 通報を解決済みにする
sensitive_account: アカウントのメディアを閲覧注意にマーク
silence_account: アカウントをサイレンス silence_account: アカウントをサイレンス
suspend_account: アカウントを停止 suspend_account: アカウントを停止
unassigned_report: 通報の担当を解除 unassigned_report: 通報の担当を解除
unsensitive_account: アカウントのメディアの閲覧注意マークを解除
unsilence_account: アカウントのサイレンスを解除 unsilence_account: アカウントのサイレンスを解除
unsuspend_account: アカウントの停止を解除 unsuspend_account: アカウントの停止を解除
update_announcement: お知らせを更新 update_announcement: お知らせを更新
@ -256,9 +261,11 @@ ja:
reopen_report: "%{name} さんが通報 %{target} を再び開きました" reopen_report: "%{name} さんが通報 %{target} を再び開きました"
reset_password_user: "%{name} さんが %{target} さんのパスワードをリセットしました" reset_password_user: "%{name} さんが %{target} さんのパスワードをリセットしました"
resolve_report: "%{name} さんが通報 %{target} を解決済みにしました" resolve_report: "%{name} さんが通報 %{target} を解決済みにしました"
sensitive_account: "%{name} さんが %{target} さんのメディアを閲覧注意にマークしました"
silence_account: "%{name} さんが %{target} さんをサイレンスにしました" silence_account: "%{name} さんが %{target} さんをサイレンスにしました"
suspend_account: "%{name} さんが %{target} さんを停止しました" suspend_account: "%{name} さんが %{target} さんを停止しました"
unassigned_report: "%{name} さんが通報 %{target} の担当を外しました" unassigned_report: "%{name} さんが通報 %{target} の担当を外しました"
unsensitive_account: "%{name} さんが %{target} さんのメディアの閲覧注意を解除しました"
unsilence_account: "%{name} さんが %{target} さんのサイレンスを解除しました" unsilence_account: "%{name} さんが %{target} さんのサイレンスを解除しました"
unsuspend_account: "%{name} さんが %{target} さんの停止を解除しました" unsuspend_account: "%{name} さんが %{target} さんの停止を解除しました"
update_announcement: "%{name} さんがお知らせ %{target} を更新しました" update_announcement: "%{name} さんがお知らせ %{target} を更新しました"
@ -1271,6 +1278,7 @@ ja:
warning: warning:
explanation: explanation:
disable: アカウントが凍結されている間、データはそのまま残りますが、凍結が解除されるまでは何の操作もできません。 disable: アカウントが凍結されている間、データはそのまま残りますが、凍結が解除されるまでは何の操作もできません。
sensitive: あなたのアップロードしたメディアファイルとリンク先のメディアは、閲覧注意として扱われます。
silence: あなたのアカウントは制限されていますが、あなたをフォローしているユーザーのみ、このサーバー上の投稿を見ることができます。そしてあなたは様々な公開リストから除外されるかもしれません。ただし、他のユーザーは手動であなたをフォローすることができます。 silence: あなたのアカウントは制限されていますが、あなたをフォローしているユーザーのみ、このサーバー上の投稿を見ることができます。そしてあなたは様々な公開リストから除外されるかもしれません。ただし、他のユーザーは手動であなたをフォローすることができます。
suspend: あなたのアカウントは停止されています。あなたの投稿とアップロードされたメディアファイルは、このサーバーとあなたのフォロワーが参加していたサーバーから完全に削除されました。 suspend: あなたのアカウントは停止されています。あなたの投稿とアップロードされたメディアファイルは、このサーバーとあなたのフォロワーが参加していたサーバーから完全に削除されました。
get_in_touch: このメールに返信することで %{instance} のスタッフと連絡を取ることができます。 get_in_touch: このメールに返信することで %{instance} のスタッフと連絡を取ることができます。
@ -1279,11 +1287,13 @@ ja:
subject: subject:
disable: あなたのアカウント %{acct} は凍結されました disable: あなたのアカウント %{acct} は凍結されました
none: "%{acct} に対する警告" none: "%{acct} に対する警告"
sensitive: あなたのアカウント %{acct} の投稿メディアは閲覧注意とマークされました
silence: あなたのアカウント %{acct} はサイレンスにされました silence: あなたのアカウント %{acct} はサイレンスにされました
suspend: あなたのアカウント %{acct} は停止されました suspend: あなたのアカウント %{acct} は停止されました
title: title:
disable: アカウントが凍結されました disable: アカウントが凍結されました
none: 警告 none: 警告
sensitive: あなたのメディアが閲覧注意とマークされました
silence: アカウントがサイレンスにされました silence: アカウントがサイレンスにされました
suspend: アカウントが停止されました suspend: アカウントが停止されました
welcome: welcome:

View file

@ -100,6 +100,7 @@ en:
types: types:
disable: Freeze disable: Freeze
none: Send a warning none: Send a warning
sensitive: Sensitive
silence: Limit silence: Limit
suspend: Suspend suspend: Suspend
warning_preset_id: Use a warning preset warning_preset_id: Use a warning preset

View file

@ -91,6 +91,7 @@ ja:
types: types:
disable: ログインを無効化 disable: ログインを無効化
none: 何もしない none: 何もしない
sensitive: 閲覧注意
silence: サイレンス silence: サイレンス
suspend: 停止しアカウントのデータを恒久的に削除する suspend: 停止しアカウントのデータを恒久的に削除する
warning_preset_id: プリセット警告文を使用 warning_preset_id: プリセット警告文を使用

View file

@ -238,6 +238,7 @@ Rails.application.routes.draw do
resources :accounts, only: [:index, :show, :destroy] do resources :accounts, only: [:index, :show, :destroy] do
member do member do
post :enable post :enable
post :unsensitive
post :unsilence post :unsilence
post :unsuspend post :unsuspend
post :redownload post :redownload
@ -480,6 +481,7 @@ Rails.application.routes.draw do
resources :accounts, only: [:index, :show, :destroy] do resources :accounts, only: [:index, :show, :destroy] do
member do member do
post :enable post :enable
post :unsensitive
post :unsilence post :unsilence
post :unsuspend post :unsuspend
post :approve post :approve

View file

@ -1,6 +1,6 @@
// Note: You must restart bin/webpack-dev-server for changes to take effect // Note: You must restart bin/webpack-dev-server for changes to take effect
const merge = require('webpack-merge'); const { merge } = require('webpack-merge');
const sharedConfig = require('./shared'); const sharedConfig = require('./shared');
const { settings, output } = require('./configuration'); const { settings, output } = require('./configuration');

View file

@ -2,7 +2,7 @@
const path = require('path'); const path = require('path');
const { URL } = require('url'); const { URL } = require('url');
const merge = require('webpack-merge'); const { merge } = require('webpack-merge');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const OfflinePlugin = require('offline-plugin'); const OfflinePlugin = require('offline-plugin');
const TerserPlugin = require('terser-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin');

View file

@ -104,7 +104,8 @@ module.exports = {
chunkFilename: 'css/[name]-[contenthash:8].chunk.css', chunkFilename: 'css/[name]-[contenthash:8].chunk.css',
}), }),
new AssetsManifestPlugin({ new AssetsManifestPlugin({
integrity: false, integrity: true,
integrityHashes: ['sha256'],
entrypoints: true, entrypoints: true,
writeToDisk: true, writeToDisk: true,
publicPath: true, publicPath: true,

View file

@ -1,6 +1,6 @@
// Note: You must restart bin/webpack-dev-server for changes to take effect // Note: You must restart bin/webpack-dev-server for changes to take effect
const merge = require('webpack-merge'); const { merge } = require('webpack-merge');
const sharedConfig = require('./shared.js'); const sharedConfig = require('./shared.js');
module.exports = merge(sharedConfig, { module.exports = merge(sharedConfig, {

View file

@ -0,0 +1,5 @@
class AddSensitizedToAccounts < ActiveRecord::Migration[5.2]
def change
add_column :accounts, :sensitized_at, :datetime
end
end

View file

@ -189,6 +189,7 @@ ActiveRecord::Schema.define(version: 2020_10_08_220312) do
t.integer "avatar_storage_schema_version" t.integer "avatar_storage_schema_version"
t.integer "header_storage_schema_version" t.integer "header_storage_schema_version"
t.string "devices_url" t.string "devices_url"
t.datetime "sensitized_at"
t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
t.index "lower((username)::text), COALESCE(lower((domain)::text), ''::text)", name: "index_accounts_on_username_and_domain_lower", unique: true t.index "lower((username)::text), COALESCE(lower((domain)::text), ''::text)", name: "index_accounts_on_username_and_domain_lower", unique: true
t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id" t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id"

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
module Webpacker::HelperExtensions
def javascript_pack_tag(name, **options)
src, integrity = current_webpacker_instance.manifest.lookup!(name, type: :javascript, with_integrity: true)
javascript_include_tag(src, options.merge(integrity: integrity))
end
def stylesheet_pack_tag(name, **options)
src, integrity = current_webpacker_instance.manifest.lookup!(name, type: :stylesheet, with_integrity: true)
stylesheet_link_tag(src, options.merge(integrity: integrity))
end
def preload_pack_asset(name, **options)
src, integrity = current_webpacker_instance.manifest.lookup!(name, with_integrity: true)
preload_link_tag(src, options.merge(integrity: integrity))
end
end
Webpacker::Helper.prepend(Webpacker::HelperExtensions)

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
module Webpacker::ManifestExtensions
def lookup(name, pack_type = {})
asset = super
if pack_type[:with_integrity] && asset.respond_to?(:dig)
[asset.dig('src'), asset.dig('integrity')]
elsif asset.respond_to?(:dig)
asset.dig('src')
else
asset
end
end
end
Webpacker::Manifest.prepend(Webpacker::ManifestExtensions)

View file

@ -85,11 +85,11 @@
"babel-runtime": "^6.26.0", "babel-runtime": "^6.26.0",
"blurhash": "^1.1.3", "blurhash": "^1.1.3",
"classnames": "^2.2.5", "classnames": "^2.2.5",
"compression-webpack-plugin": "^6.0.3", "compression-webpack-plugin": "^6.0.4",
"cross-env": "^7.0.2", "cross-env": "^7.0.2",
"css-loader": "^5.0.0", "css-loader": "^5.0.0",
"cssnano": "^4.1.10", "cssnano": "^4.1.10",
"detect-passive-events": "^1.0.5", "detect-passive-events": "^2.0.1",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"emoji-mart": "Gargron/emoji-mart#build", "emoji-mart": "Gargron/emoji-mart#build",
"es6-symbol": "^3.1.3", "es6-symbol": "^3.1.3",
@ -97,7 +97,7 @@
"exif-js": "^2.3.0", "exif-js": "^2.3.0",
"express": "^4.17.1", "express": "^4.17.1",
"favico.js": "^0.3.10", "favico.js": "^0.3.10",
"file-loader": "^6.1.1", "file-loader": "^6.2.0",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"glob": "^7.1.6", "glob": "^7.1.6",
"history": "^4.10.1", "history": "^4.10.1",
@ -113,7 +113,7 @@
"lodash": "^4.17.19", "lodash": "^4.17.19",
"mark-loader": "^0.1.6", "mark-loader": "^0.1.6",
"marky": "^1.2.1", "marky": "^1.2.1",
"mini-css-extract-plugin": "^1.2.0", "mini-css-extract-plugin": "^1.2.1",
"mkdirp": "^1.0.4", "mkdirp": "^1.0.4",
"npmlog": "^4.1.2", "npmlog": "^4.1.2",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
@ -137,7 +137,7 @@
"react-motion": "^0.5.2", "react-motion": "^0.5.2",
"react-notification": "^6.8.5", "react-notification": "^6.8.5",
"react-overlays": "^0.9.2", "react-overlays": "^0.9.2",
"react-redux": "^7.2.1", "react-redux": "^7.2.2",
"react-redux-loading-bar": "^4.0.8", "react-redux-loading-bar": "^4.0.8",
"react-router-dom": "^4.1.1", "react-router-dom": "^4.1.1",
"react-router-scroll-4": "^1.0.0-beta.1", "react-router-scroll-4": "^1.0.0-beta.1",
@ -155,7 +155,7 @@
"requestidlecallback": "^0.3.0", "requestidlecallback": "^0.3.0",
"reselect": "^4.0.0", "reselect": "^4.0.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"sass": "^1.27.0", "sass": "^1.28.0",
"sass-loader": "^10.0.4", "sass-loader": "^10.0.4",
"stacktrace-js": "^2.0.2", "stacktrace-js": "^2.0.2",
"stringz": "^2.1.0", "stringz": "^2.1.0",
@ -169,17 +169,17 @@
"webpack-assets-manifest": "^3.1.1", "webpack-assets-manifest": "^3.1.1",
"webpack-bundle-analyzer": "^3.9.0", "webpack-bundle-analyzer": "^3.9.0",
"webpack-cli": "^3.3.12", "webpack-cli": "^3.3.12",
"webpack-merge": "^4.2.1", "webpack-merge": "^5.0.9",
"wicg-inert": "^3.0.3" "wicg-inert": "^3.1.0"
}, },
"devDependencies": { "devDependencies": {
"@testing-library/jest-dom": "^5.11.5", "@testing-library/jest-dom": "^5.11.5",
"@testing-library/react": "^11.1.0", "@testing-library/react": "^11.1.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"babel-jest": "^26.6.1", "babel-jest": "^26.6.1",
"eslint": "^7.12.0", "eslint": "^7.12.1",
"eslint-plugin-import": "~2.22.1", "eslint-plugin-import": "~2.22.1",
"eslint-plugin-jsx-a11y": "~6.3.1", "eslint-plugin-jsx-a11y": "~6.4.1",
"eslint-plugin-promise": "~4.2.1", "eslint-plugin-promise": "~4.2.1",
"eslint-plugin-react": "~7.21.5", "eslint-plugin-react": "~7.21.5",
"jest": "^26.6.1", "jest": "^26.6.1",

View file

@ -127,6 +127,24 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
end end
end end
describe 'POST #unsensitive' do
before do
account.touch(:sensitized_at)
post :unsensitive, params: { id: account.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', 'user'
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'unsensitives account' do
expect(account.reload.sensitized?).to be false
end
end
describe 'POST #unsilence' do describe 'POST #unsilence' do
before do before do
account.touch(:silenced_at) account.touch(:silenced_at)

View file

@ -115,16 +115,16 @@ RSpec.describe Admin::AccountAction, type: :model do
context 'account.local?' do context 'account.local?' do
let(:account) { Fabricate(:account, domain: nil) } let(:account) { Fabricate(:account, domain: nil) }
it 'returns ["none", "disable", "silence", "suspend"]' do it 'returns ["none", "disable", "sensitive", "silence", "suspend"]' do
expect(subject).to eq %w(none disable silence suspend) expect(subject).to eq %w(none disable sensitive silence suspend)
end end
end end
context '!account.local?' do context '!account.local?' do
let(:account) { Fabricate(:account, domain: 'hoge.com') } let(:account) { Fabricate(:account, domain: 'hoge.com') }
it 'returns ["silence", "suspend"]' do it 'returns ["sensitive", "silence", "suspend"]' do
expect(subject).to eq %w(silence suspend) expect(subject).to eq %w(sensitive silence suspend)
end end
end end
end end

View file

@ -8,7 +8,7 @@ RSpec.describe AccountPolicy do
let(:admin) { Fabricate(:user, admin: true).account } let(:admin) { Fabricate(:user, admin: true).account }
let(:john) { Fabricate(:user).account } let(:john) { Fabricate(:user).account }
permissions :index?, :show?, :unsuspend?, :unsilence?, :remove_avatar?, :remove_header? do permissions :index?, :show?, :unsuspend?, :unsensitive?, :unsilence?, :remove_avatar?, :remove_header? do
context 'staff' do context 'staff' do
it 'permits' do it 'permits' do
expect(subject).to permit(admin) expect(subject).to permit(admin)

164
yarn.lock
View file

@ -1080,10 +1080,10 @@
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.4.tgz#622a72bebd1e3f48d921563b4b60a762295a81fc" resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.4.tgz#622a72bebd1e3f48d921563b4b60a762295a81fc"
integrity sha512-6PYY5DVdAY1ifaQW6XYTnOMihmBVT27elqSjEoodchsGjzYlEsTQMcEhSud99kVawatyTZRTiVkJ/c6lwbQ7nA== integrity sha512-6PYY5DVdAY1ifaQW6XYTnOMihmBVT27elqSjEoodchsGjzYlEsTQMcEhSud99kVawatyTZRTiVkJ/c6lwbQ7nA==
"@eslint/eslintrc@^0.2.0": "@eslint/eslintrc@^0.2.1":
version "0.2.0" version "0.2.1"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.2.0.tgz#bc7e3c4304d4c8720968ccaee793087dfb5fe6b4" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.2.1.tgz#f72069c330461a06684d119384435e12a5d76e3c"
integrity sha512-+cIGPCBdLCzqxdtwppswP+zTsH9BOIGzAeKfBIbtb4gW/giMlfMwP0HUSFfhzh20f9u8uZ8hOp62+4GPquTbwQ== integrity sha512-XRUeBZ5zBWLYgSANMpThFddrZZkEbGHgUdt5UJjZfnlN9BGCiUBrf+nvbRupSjMvqzwnQN0qwCmOxITt1cfywA==
dependencies: dependencies:
ajv "^6.12.4" ajv "^6.12.4"
debug "^4.1.1" debug "^4.1.1"
@ -2100,10 +2100,10 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428"
integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==
axe-core@^3.5.4: axe-core@^4.0.2:
version "3.5.5" version "4.0.2"
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-3.5.5.tgz#84315073b53fa3c0c51676c588d59da09a192227" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.0.2.tgz#c7cf7378378a51fcd272d3c09668002a4990b1cb"
integrity sha512-5P0QZ6J5xGikH780pghEdbEKijCTrruK9KxtPZCFWUpef0f6GipO+xEZ5GKCb020mmqgbiNO6TcA55CriL784Q== integrity sha512-arU1h31OGFu+LPrOLGZ7nB45v940NMDMEJeNmbutu57P+UFDVnkZg3e+J1I2HJRZ9hT7gO8J91dn/PMrAiKakA==
axios@^0.21.0: axios@^0.21.0:
version "0.21.0" version "0.21.0"
@ -2112,7 +2112,7 @@ axios@^0.21.0:
dependencies: dependencies:
follow-redirects "^1.10.0" follow-redirects "^1.10.0"
axobject-query@^2.1.2: axobject-query@^2.2.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==
@ -2894,6 +2894,15 @@ cliui@^7.0.2:
strip-ansi "^6.0.0" strip-ansi "^6.0.0"
wrap-ansi "^7.0.0" wrap-ansi "^7.0.0"
clone-deep@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
dependencies:
is-plain-object "^2.0.4"
kind-of "^6.0.2"
shallow-clone "^3.0.0"
co@^4.6.0: co@^4.6.0:
version "4.6.0" version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@ -3000,10 +3009,10 @@ compressible@~2.0.16:
dependencies: dependencies:
mime-db ">= 1.43.0 < 2" mime-db ">= 1.43.0 < 2"
compression-webpack-plugin@^6.0.3: compression-webpack-plugin@^6.0.4:
version "6.0.3" version "6.0.4"
resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-6.0.3.tgz#d0d3e913810e3bf67462e1cecd794b3109af89de" resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-6.0.4.tgz#524699c0ad4e94cab0eb199c734e291f6ab685b9"
integrity sha512-xzSWiZWwBs+HHGhlYxw0oFaYL/0VYErEqDHCAJhJ3Mza5fmF5JJ4iaB6Ap2JT68C0UhhmoI4Mh37LVz/THv2Fw== integrity sha512-PViPdrF5UmqZxsr9WNoE+R6lTre6/5tC9TmWotBfhOQtWlc7oj/SXCsrecbZJ9LDpwLjHH6llPCKmw+JGPGN+A==
dependencies: dependencies:
cacache "^15.0.5" cacache "^15.0.5"
find-cache-dir "^3.3.1" find-cache-dir "^3.3.1"
@ -3644,10 +3653,10 @@ detect-node@^2.0.4:
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
detect-passive-events@^1.0.5: detect-passive-events@^2.0.1:
version "1.0.5" version "2.0.1"
resolved "https://registry.yarnpkg.com/detect-passive-events/-/detect-passive-events-1.0.5.tgz#ce324db665123bef9e368b8059ff95d95217cc05" resolved "https://registry.yarnpkg.com/detect-passive-events/-/detect-passive-events-2.0.1.tgz#fdbd6f6dd5e6ac10c6189a4cb26ab264d41c0835"
integrity sha512-foW7Q35wwOCxVzW0xLf5XeB5Fhe7oyRgvkBYdiP9IWgLMzjqUqTvsJv9ymuEWGjY6AoDXD3OC294+Z9iuOw0QA== integrity sha512-7WbRn4mznO63FW0KSYa7S3HgCG94uZ6HGZO1TyVRtdZuMNGUeY/ScWrIx45XnUz1LWoLZVi13ULVHqKE07ZfKg==
diff-sequences@^25.2.6: diff-sequences@^25.2.6:
version "25.2.6" version "25.2.6"
@ -4107,21 +4116,21 @@ eslint-plugin-import@~2.22.1:
resolve "^1.17.0" resolve "^1.17.0"
tsconfig-paths "^3.9.0" tsconfig-paths "^3.9.0"
eslint-plugin-jsx-a11y@~6.3.1: eslint-plugin-jsx-a11y@~6.4.1:
version "6.3.1" version "6.4.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.3.1.tgz#99ef7e97f567cc6a5b8dd5ab95a94a67058a2660" resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz#a2d84caa49756942f42f1ffab9002436391718fd"
integrity sha512-i1S+P+c3HOlBJzMFORRbC58tHa65Kbo8b52/TwCwSKLohwvpfT5rm2GjGWzOHTEuq4xxf2aRlHHTtmExDQOP+g== integrity sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg==
dependencies: dependencies:
"@babel/runtime" "^7.10.2" "@babel/runtime" "^7.11.2"
aria-query "^4.2.2" aria-query "^4.2.2"
array-includes "^3.1.1" array-includes "^3.1.1"
ast-types-flow "^0.0.7" ast-types-flow "^0.0.7"
axe-core "^3.5.4" axe-core "^4.0.2"
axobject-query "^2.1.2" axobject-query "^2.2.0"
damerau-levenshtein "^1.0.6" damerau-levenshtein "^1.0.6"
emoji-regex "^9.0.0" emoji-regex "^9.0.0"
has "^1.0.3" has "^1.0.3"
jsx-ast-utils "^2.4.1" jsx-ast-utils "^3.1.0"
language-tags "^1.0.5" language-tags "^1.0.5"
eslint-plugin-promise@~4.2.1: eslint-plugin-promise@~4.2.1:
@ -4218,13 +4227,13 @@ eslint@^2.7.0:
text-table "~0.2.0" text-table "~0.2.0"
user-home "^2.0.0" user-home "^2.0.0"
eslint@^7.12.0: eslint@^7.12.1:
version "7.12.0" version "7.12.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.12.0.tgz#7b6a85f87a9adc239e979bb721cde5ce0dc27da6" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.12.1.tgz#bd9a81fa67a6cfd51656cdb88812ce49ccec5801"
integrity sha512-n5pEU27DRxCSlOhJ2rO57GDLcNsxO0LPpAbpFdh7xmcDmjmlGUfoyrsB3I7yYdQXO5N3gkSTiDrPSPNFiiirXA== integrity sha512-HlMTEdr/LicJfN08LB3nM1rRYliDXOmfoO4vj39xN6BLpFzF00hbwBoqHk8UcJ2M/3nlARZWy/mslvGEuZFvsg==
dependencies: dependencies:
"@babel/code-frame" "^7.0.0" "@babel/code-frame" "^7.0.0"
"@eslint/eslintrc" "^0.2.0" "@eslint/eslintrc" "^0.2.1"
ajv "^6.10.0" ajv "^6.10.0"
chalk "^4.0.0" chalk "^4.0.0"
cross-spawn "^7.0.2" cross-spawn "^7.0.2"
@ -4586,10 +4595,10 @@ file-entry-cache@^5.0.1:
dependencies: dependencies:
flat-cache "^2.0.1" flat-cache "^2.0.1"
file-loader@^6.1.1: file-loader@^6.2.0:
version "6.1.1" version "6.2.0"
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.1.1.tgz#a6f29dfb3f5933a1c350b2dbaa20ac5be0539baa" resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d"
integrity sha512-Klt8C4BjWSXYQAfhpYYkG4qHNTna4toMHEbWrI5IuVoxbU6uiDKeKAP99R8mmbJi3lvewn/jQBOgU4+NS3tDQw== integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==
dependencies: dependencies:
loader-utils "^2.0.0" loader-utils "^2.0.0"
schema-utils "^3.0.0" schema-utils "^3.0.0"
@ -5196,10 +5205,10 @@ hoist-non-react-statics@^2.5.0:
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==
hoist-non-react-statics@^3.3.0: hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.0" version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA== integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
dependencies: dependencies:
react-is "^16.7.0" react-is "^16.7.0"
@ -6576,18 +6585,10 @@ jsprim@^1.2.2:
json-schema "0.2.3" json-schema "0.2.3"
verror "1.10.0" verror "1.10.0"
jsx-ast-utils@^2.4.1: "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.1.0:
version "2.4.1" version "3.1.0"
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz#1114a4c1209481db06c690c2b4f488cc665f657e" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz#642f1d7b88aa6d7eb9d8f2210e166478444fa891"
integrity sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w== integrity sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA==
dependencies:
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: dependencies:
array-includes "^3.1.1" array-includes "^3.1.1"
object.assign "^4.1.1" object.assign "^4.1.1"
@ -7006,10 +7007,10 @@ min-indent@^1.0.0:
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
mini-css-extract-plugin@^1.2.0: mini-css-extract-plugin@^1.2.1:
version "1.2.0" version "1.2.1"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.2.0.tgz#f1bdfa7bb6f6a238bc327f813f204283ea33ee36" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.2.1.tgz#30ea7dee632b3002b0c77aeed447790408cb247e"
integrity sha512-iBZokjaIjHvI4N0AURx5aPBawcmxB/d2NYikxZ4J57Lg5sDShUPyWvuSWl1dueI5oCs7nz8V7qtOCaLjB7AYPw== integrity sha512-G3yw7/TQaPfkuiR73MDcyiqhyP8SnbmLhUbpC76H+wtQxA6wfKhMCQOCb6wnPK0dQbjORAeOILQqEesg4/wF7A==
dependencies: dependencies:
loader-utils "^2.0.0" loader-utils "^2.0.0"
schema-utils "^3.0.0" schema-utils "^3.0.0"
@ -8679,7 +8680,7 @@ react-intl@^2.9.0:
intl-relativeformat "^2.1.0" intl-relativeformat "^2.1.0"
invariant "^2.1.1" invariant "^2.1.1"
react-is@^16.12.0, react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6, react-is@^16.9.0: react-is@^16.12.0, react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6:
version "16.13.1" version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@ -8739,16 +8740,16 @@ react-redux-loading-bar@^4.0.8:
prop-types "^15.6.2" prop-types "^15.6.2"
react-lifecycles-compat "^3.0.2" react-lifecycles-compat "^3.0.2"
react-redux@^7.2.1: react-redux@^7.2.2:
version "7.2.1" version "7.2.2"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.1.tgz#8dedf784901014db2feca1ab633864dee68ad985" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.2.tgz#03862e803a30b6b9ef8582dadcc810947f74b736"
integrity sha512-T+VfD/bvgGTUA74iW9d2i5THrDQWbweXP0AVNI8tNd1Rk5ch1rnMiJkDD67ejw7YBKM4+REvcvqRuWJb7BLuEg== integrity sha512-8+CQ1EvIVFkYL/vu6Olo7JFLWop1qRUeb46sGtIMDCSpgwPQq8fPLpirIB0iTqFe9XYEFPHssdX8/UwN6pAkEA==
dependencies: dependencies:
"@babel/runtime" "^7.5.5" "@babel/runtime" "^7.12.1"
hoist-non-react-statics "^3.3.0" hoist-non-react-statics "^3.3.2"
loose-envify "^1.4.0" loose-envify "^1.4.0"
prop-types "^15.7.2" prop-types "^15.7.2"
react-is "^16.9.0" react-is "^16.13.1"
react-router-dom@^4.1.1: react-router-dom@^4.1.1:
version "4.3.1" version "4.3.1"
@ -9424,10 +9425,10 @@ sass-loader@^10.0.4:
schema-utils "^3.0.0" schema-utils "^3.0.0"
semver "^7.3.2" semver "^7.3.2"
sass@^1.27.0: sass@^1.28.0:
version "1.27.0" version "1.28.0"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.27.0.tgz#0657ff674206b95ec20dc638a93e179c78f6ada2" resolved "https://registry.yarnpkg.com/sass/-/sass-1.28.0.tgz#546f1308ff74cc4ec2ad735fd35dc18bc3f51f72"
integrity sha512-0gcrER56OkzotK/GGwgg4fPrKuiFlPNitO7eUJ18Bs+/NBlofJfMxmxqpqJxjae9vu0Wq8TZzrSyxZal00WDig== integrity sha512-9FWX/0wuE1KxwfiP02chZhHaPzu6adpx9+wGch7WMOuHy5npOo0UapRI3FNSHva2CczaYJu2yNUBN8cCSqHz/A==
dependencies: dependencies:
chokidar ">=2.0.0 <4.0.0" chokidar ">=2.0.0 <4.0.0"
@ -9615,6 +9616,13 @@ sha.js@^2.4.0, sha.js@^2.4.8:
inherits "^2.0.1" inherits "^2.0.1"
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
shallow-clone@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
dependencies:
kind-of "^6.0.2"
shallow-equal@^1.2.1: shallow-equal@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da" resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da"
@ -11048,12 +11056,13 @@ webpack-log@^2.0.0:
ansi-colors "^3.0.0" ansi-colors "^3.0.0"
uuid "^3.3.2" uuid "^3.3.2"
webpack-merge@^4.2.1: webpack-merge@^5.0.9:
version "4.2.2" version "5.0.9"
resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d" resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.0.9.tgz#d5e0e0ae564ae704836d747893bdd2741544bf31"
integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== integrity sha512-P4teh6O26xIDPugOGX61wPxaeP918QOMjmzhu54zTVcLtOS28ffPWtnv+ilt3wscwBUCL2WNMnh97XkrKqt9Fw==
dependencies: dependencies:
lodash "^4.17.15" clone-deep "^4.0.1"
wildcard "^2.0.0"
webpack-sources@^1.0.0, webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: webpack-sources@^1.0.0, webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3:
version "1.4.3" version "1.4.3"
@ -11153,10 +11162,10 @@ which@^2.0.1, which@^2.0.2:
dependencies: dependencies:
isexe "^2.0.0" isexe "^2.0.0"
wicg-inert@^3.0.3: wicg-inert@^3.1.0:
version "3.0.3" version "3.1.0"
resolved "https://registry.yarnpkg.com/wicg-inert/-/wicg-inert-3.0.3.tgz#7d05eaed64176887ee4c66fc0c4d6fe4b38ccce5" resolved "https://registry.yarnpkg.com/wicg-inert/-/wicg-inert-3.1.0.tgz#6525f12db188b83f0051bed2ddcf6c1aa5b17590"
integrity sha512-XwXf8K0NN4cpagjBlZ2/j/5Sjf6dW3HNbfywEy1y6Z8PJKvSHVGiuc5Id/9RZ6EmGq+GQCGTo7B2SK0Misbr6g== integrity sha512-P0ZiWaN9SxOkJbYtF/PIwmIRO8UTqTJtyl33QTQlHfAb6h15T0Dp5m7WTJ8N6UWIoj+KU5M0a8EtfRZLlHiP0Q==
wide-align@^1.1.0: wide-align@^1.1.0:
version "1.1.3" version "1.1.3"
@ -11165,6 +11174,11 @@ wide-align@^1.1.0:
dependencies: dependencies:
string-width "^1.0.2 || 2" string-width "^1.0.2 || 2"
wildcard@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec"
integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==
word-wrap@^1.2.3, word-wrap@~1.2.3: word-wrap@^1.2.3, word-wrap@~1.2.3:
version "1.2.3" version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"