Merge pull request #2747 from ClearlyClaire/glitch-soc/merge-upstream

Merge upstream changes up to 58ace2e45e
main
Claire 2024-06-18 19:25:49 +02:00 committed by GitHub
commit 3bc513c06b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 438 additions and 258 deletions

View File

@ -4,14 +4,9 @@ AllCops:
DisplayCopNames: true DisplayCopNames: true
DisplayStyleGuide: true DisplayStyleGuide: true
Exclude: Exclude:
- db/schema.rb
- bin/*
- node_modules/**/*
- Vagrantfile - Vagrantfile
- vendor/**/*
- config/initializers/json_ld* - config/initializers/json_ld*
- lib/mastodon/migration_helpers.rb - lib/mastodon/migration_helpers.rb
- lib/templates/**/*
ExtraDetails: true ExtraDetails: true
NewCops: enable NewCops: enable
TargetRubyVersion: 3.1 # Oldest supported ruby version TargetRubyVersion: 3.1 # Oldest supported ruby version

View File

@ -1,4 +1,4 @@
# syntax=docker/dockerfile:1.7 # syntax=docker/dockerfile:1.8
# This file is designed for production server deployment, not local development work # This file is designed for production server deployment, not local development work
# For a containerized local dev environment, see: https://github.com/mastodon/mastodon/blob/main/README.md#docker # For a containerized local dev environment, see: https://github.com/mastodon/mastodon/blob/main/README.md#docker
@ -24,10 +24,10 @@ FROM docker.io/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim as node
FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} as ruby FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} as ruby
# Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA # Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA
# Example: v4.2.0-nightly.2023.11.09+something # Example: v4.3.0-nightly.2023.11.09+pr-123456
# Overwrite existence of 'alpha.0' in version.rb [--build-arg MASTODON_VERSION_PRERELEASE="nightly.2023.11.09"] # Overwrite existence of 'alpha.X' in version.rb [--build-arg MASTODON_VERSION_PRERELEASE="nightly.2023.11.09"]
ARG MASTODON_VERSION_PRERELEASE="" ARG MASTODON_VERSION_PRERELEASE=""
# Append build metadata or fork information to version.rb [--build-arg MASTODON_VERSION_METADATA="pr-12345"] # Append build metadata or fork information to version.rb [--build-arg MASTODON_VERSION_METADATA="pr-123456"]
ARG MASTODON_VERSION_METADATA="" ARG MASTODON_VERSION_METADATA=""
# Allow Ruby on Rails to serve static files # Allow Ruby on Rails to serve static files
@ -100,9 +100,7 @@ RUN \
apt-get dist-upgrade -yq; \ apt-get dist-upgrade -yq; \
# Install jemalloc, curl and other necessary components # Install jemalloc, curl and other necessary components
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
ca-certificates \
curl \ curl \
ffmpeg \
file \ file \
libjemalloc2 \ libjemalloc2 \
patchelf \ patchelf \
@ -137,7 +135,10 @@ RUN \
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \ --mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
# Install build tools and bundler dependencies from APT # Install build tools and bundler dependencies from APT
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
autoconf \
automake \
build-essential \ build-essential \
cmake \
git \ git \
libgdbm-dev \ libgdbm-dev \
libglib2.0-dev \ libglib2.0-dev \
@ -146,9 +147,12 @@ RUN \
libidn-dev \ libidn-dev \
libpq-dev \ libpq-dev \
libssl-dev \ libssl-dev \
libtool \
meson \ meson \
nasm \
pkg-config \ pkg-config \
shared-mime-info \ shared-mime-info \
xz-utils \
# libvips components # libvips components
libcgif-dev \ libcgif-dev \
libexif-dev \ libexif-dev \
@ -162,6 +166,16 @@ RUN \
libspng-dev \ libspng-dev \
libtiff-dev \ libtiff-dev \
libwebp-dev \ libwebp-dev \
# ffmpeg components
libdav1d-dev \
liblzma-dev \
libmp3lame-dev \
libopus-dev \
libsnappy-dev \
libvorbis-dev \
libvpx-dev \
libx264-dev \
libx265-dev \
; ;
RUN \ RUN \
@ -190,6 +204,48 @@ RUN \
ninja; \ ninja; \
ninja install; ninja install;
# Create temporary ffmpeg specific build layer from build layer
FROM build as ffmpeg
# ffmpeg version to compile, change with [--build-arg FFMPEG_VERSION="7.0.x"]
# renovate: datasource=repology depName=ffmpeg packageName=openpkg_current/ffmpeg
ARG FFMPEG_VERSION=7.0.1
# ffmpeg download URL, change with [--build-arg FFMPEG_URL="https://ffmpeg.org/releases"]
ARG FFMPEG_URL=https://ffmpeg.org/releases
WORKDIR /usr/local/ffmpeg/src
RUN \
curl -sSL -o ffmpeg-${FFMPEG_VERSION}.tar.xz ${FFMPEG_URL}/ffmpeg-${FFMPEG_VERSION}.tar.xz; \
tar xf ffmpeg-${FFMPEG_VERSION}.tar.xz; \
cd ffmpeg-${FFMPEG_VERSION}; \
./configure \
--prefix=/usr/local/ffmpeg \
--toolchain=hardened \
--disable-debug \
--disable-devices \
--disable-doc \
--disable-ffplay \
--disable-network \
--disable-static \
--enable-ffmpeg \
--enable-ffprobe \
--enable-gpl \
--enable-libdav1d \
--enable-libmp3lame \
--enable-libopus \
--enable-libsnappy \
--enable-libvorbis \
--enable-libvpx \
--enable-libwebp \
--enable-libx264 \
--enable-libx265 \
--enable-shared \
--enable-version3 \
; \
make -j$(nproc); \
make install;
# Create temporary bundler specific build layer from build layer # Create temporary bundler specific build layer from build layer
FROM build as bundler FROM build as bundler
@ -289,6 +345,20 @@ RUN \
libwebp7 \ libwebp7 \
libwebpdemux2 \ libwebpdemux2 \
libwebpmux3 \ libwebpmux3 \
# ffmpeg components
libdav1d6 \
libmp3lame0 \
libopencore-amrnb0 \
libopencore-amrwb0 \
libopus0 \
libsnappy1v5 \
libtheora0 \
libvorbis0a \
libvorbisenc2 \
libvorbisfile3 \
libvpx7 \
libx264-164 \
libx265-199 \
; ;
# Copy Mastodon sources into final layer # Copy Mastodon sources into final layer
@ -302,11 +372,16 @@ COPY --from=bundler /usr/local/bundle/ /usr/local/bundle/
# Copy libvips components to layer # Copy libvips components to layer
COPY --from=libvips /usr/local/libvips/bin /usr/local/bin COPY --from=libvips /usr/local/libvips/bin /usr/local/bin
COPY --from=libvips /usr/local/libvips/lib /usr/local/lib COPY --from=libvips /usr/local/libvips/lib /usr/local/lib
# Copy ffpmeg components to layer
COPY --from=ffmpeg /usr/local/ffmpeg/bin /usr/local/bin
COPY --from=ffmpeg /usr/local/ffmpeg/lib /usr/local/lib
RUN \ RUN \
ldconfig; \ ldconfig; \
# Smoketest media processors # Smoketest media processors
vips -v; vips -v; \
ffmpeg -version; \
ffprobe -version;
RUN \ RUN \
# Precompile bootsnap code for faster Rails startup # Precompile bootsnap code for faster Rails startup

View File

@ -9,9 +9,6 @@ gem 'rack', '~> 2.2.7'
gem 'rails', '~> 7.1.1' gem 'rails', '~> 7.1.1'
gem 'thor', '~> 1.2' gem 'thor', '~> 1.2'
# For why irb is in the Gemfile, see: https://ruby.social/@st0012/111444685161478182
gem 'irb', '~> 1.8'
gem 'dotenv' gem 'dotenv'
gem 'haml-rails', '~>2.0' gem 'haml-rails', '~>2.0'
gem 'pg', '~> 1.5' gem 'pg', '~> 1.5'
@ -61,6 +58,7 @@ gem 'httplog', '~> 1.7.0'
gem 'i18n' gem 'i18n'
gem 'idn-ruby', require: 'idn' gem 'idn-ruby', require: 'idn'
gem 'inline_svg' gem 'inline_svg'
gem 'irb', '~> 1.8'
gem 'kaminari', '~> 1.2' gem 'kaminari', '~> 1.2'
gem 'link_header', '~> 0.0' gem 'link_header', '~> 0.0'
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'

View File

@ -0,0 +1,16 @@
import {
apiGetNotificationPolicy,
apiUpdateNotificationsPolicy,
} from 'flavours/glitch/api/notification_policies';
import type { NotificationPolicy } from 'flavours/glitch/models/notification_policy';
import { createDataLoadingThunk } from 'flavours/glitch/store/typed_functions';
export const fetchNotificationPolicy = createDataLoadingThunk(
'notificationPolicy/fetch',
() => apiGetNotificationPolicy(),
);
export const updateNotificationsPolicy = createDataLoadingThunk(
'notificationPolicy/update',
(policy: Partial<NotificationPolicy>) => apiUpdateNotificationsPolicy(policy),
);

View File

@ -57,10 +57,6 @@ export const NOTIFICATIONS_MARK_AS_READ = 'NOTIFICATIONS_MARK_AS_READ';
export const NOTIFICATIONS_SET_BROWSER_SUPPORT = 'NOTIFICATIONS_SET_BROWSER_SUPPORT'; export const NOTIFICATIONS_SET_BROWSER_SUPPORT = 'NOTIFICATIONS_SET_BROWSER_SUPPORT';
export const NOTIFICATIONS_SET_BROWSER_PERMISSION = 'NOTIFICATIONS_SET_BROWSER_PERMISSION'; export const NOTIFICATIONS_SET_BROWSER_PERMISSION = 'NOTIFICATIONS_SET_BROWSER_PERMISSION';
export const NOTIFICATION_POLICY_FETCH_REQUEST = 'NOTIFICATION_POLICY_FETCH_REQUEST';
export const NOTIFICATION_POLICY_FETCH_SUCCESS = 'NOTIFICATION_POLICY_FETCH_SUCCESS';
export const NOTIFICATION_POLICY_FETCH_FAIL = 'NOTIFICATION_POLICY_FETCH_FAIL';
export const NOTIFICATION_REQUESTS_FETCH_REQUEST = 'NOTIFICATION_REQUESTS_FETCH_REQUEST'; export const NOTIFICATION_REQUESTS_FETCH_REQUEST = 'NOTIFICATION_REQUESTS_FETCH_REQUEST';
export const NOTIFICATION_REQUESTS_FETCH_SUCCESS = 'NOTIFICATION_REQUESTS_FETCH_SUCCESS'; export const NOTIFICATION_REQUESTS_FETCH_SUCCESS = 'NOTIFICATION_REQUESTS_FETCH_SUCCESS';
export const NOTIFICATION_REQUESTS_FETCH_FAIL = 'NOTIFICATION_REQUESTS_FETCH_FAIL'; export const NOTIFICATION_REQUESTS_FETCH_FAIL = 'NOTIFICATION_REQUESTS_FETCH_FAIL';
@ -434,40 +430,6 @@ export function setBrowserPermission (value) {
}; };
} }
export const fetchNotificationPolicy = () => (dispatch) => {
dispatch(fetchNotificationPolicyRequest());
api().get('/api/v1/notifications/policy').then(({ data }) => {
dispatch(fetchNotificationPolicySuccess(data));
}).catch(err => {
dispatch(fetchNotificationPolicyFail(err));
});
};
export const fetchNotificationPolicyRequest = () => ({
type: NOTIFICATION_POLICY_FETCH_REQUEST,
});
export const fetchNotificationPolicySuccess = policy => ({
type: NOTIFICATION_POLICY_FETCH_SUCCESS,
policy,
});
export const fetchNotificationPolicyFail = error => ({
type: NOTIFICATION_POLICY_FETCH_FAIL,
error,
});
export const updateNotificationsPolicy = params => (dispatch) => {
dispatch(fetchNotificationPolicyRequest());
api().put('/api/v1/notifications/policy', params).then(({ data }) => {
dispatch(fetchNotificationPolicySuccess(data));
}).catch(err => {
dispatch(fetchNotificationPolicyFail(err));
});
};
export const fetchNotificationRequests = () => (dispatch, getState) => { export const fetchNotificationRequests = () => (dispatch, getState) => {
const params = {}; const params = {};

View File

@ -0,0 +1,10 @@
import { apiRequest } from 'flavours/glitch/api';
import type { NotificationPolicyJSON } from 'flavours/glitch/api_types/notification_policies';
export const apiGetNotificationPolicy = () =>
apiRequest<NotificationPolicyJSON>('GET', '/v1/notifications/policy');
export const apiUpdateNotificationsPolicy = (
policy: Partial<NotificationPolicyJSON>,
) =>
apiRequest<NotificationPolicyJSON>('PUT', '/v1/notifications/policy', policy);

View File

@ -0,0 +1,12 @@
// See app/serializers/rest/notification_policy_serializer.rb
export interface NotificationPolicyJSON {
filter_not_following: boolean;
filter_not_followers: boolean;
filter_new_accounts: boolean;
filter_private_mentions: boolean;
summary: {
pending_requests_count: number;
pending_notifications_count: number;
};
}

View File

@ -25,7 +25,7 @@ class ColumnSettings extends PureComponent {
alertsEnabled: PropTypes.bool, alertsEnabled: PropTypes.bool,
browserSupport: PropTypes.bool, browserSupport: PropTypes.bool,
browserPermission: PropTypes.string, browserPermission: PropTypes.string,
notificationPolicy: ImmutablePropTypes.map, notificationPolicy: PropTypes.object.isRequired,
onChangePolicy: PropTypes.func.isRequired, onChangePolicy: PropTypes.func.isRequired,
}; };
@ -84,22 +84,22 @@ class ColumnSettings extends PureComponent {
<h3><FormattedMessage id='notifications.policy.title' defaultMessage='Filter out notifications from…' /></h3> <h3><FormattedMessage id='notifications.policy.title' defaultMessage='Filter out notifications from…' /></h3>
<div className='column-settings__row'> <div className='column-settings__row'>
<CheckboxWithLabel checked={notificationPolicy.get('filter_not_following')} onChange={this.handleFilterNotFollowing}> <CheckboxWithLabel checked={notificationPolicy.filter_not_following} onChange={this.handleFilterNotFollowing}>
<strong><FormattedMessage id='notifications.policy.filter_not_following_title' defaultMessage="People you don't follow" /></strong> <strong><FormattedMessage id='notifications.policy.filter_not_following_title' defaultMessage="People you don't follow" /></strong>
<span className='hint'><FormattedMessage id='notifications.policy.filter_not_following_hint' defaultMessage='Until you manually approve them' /></span> <span className='hint'><FormattedMessage id='notifications.policy.filter_not_following_hint' defaultMessage='Until you manually approve them' /></span>
</CheckboxWithLabel> </CheckboxWithLabel>
<CheckboxWithLabel checked={notificationPolicy.get('filter_not_followers')} onChange={this.handleFilterNotFollowers}> <CheckboxWithLabel checked={notificationPolicy.filter_not_followers} onChange={this.handleFilterNotFollowers}>
<strong><FormattedMessage id='notifications.policy.filter_not_followers_title' defaultMessage='People not following you' /></strong> <strong><FormattedMessage id='notifications.policy.filter_not_followers_title' defaultMessage='People not following you' /></strong>
<span className='hint'><FormattedMessage id='notifications.policy.filter_not_followers_hint' defaultMessage='Including people who have been following you fewer than {days, plural, one {one day} other {# days}}' values={{ days: 3 }} /></span> <span className='hint'><FormattedMessage id='notifications.policy.filter_not_followers_hint' defaultMessage='Including people who have been following you fewer than {days, plural, one {one day} other {# days}}' values={{ days: 3 }} /></span>
</CheckboxWithLabel> </CheckboxWithLabel>
<CheckboxWithLabel checked={notificationPolicy.get('filter_new_accounts')} onChange={this.handleFilterNewAccounts}> <CheckboxWithLabel checked={notificationPolicy.filter_new_accounts} onChange={this.handleFilterNewAccounts}>
<strong><FormattedMessage id='notifications.policy.filter_new_accounts_title' defaultMessage='New accounts' /></strong> <strong><FormattedMessage id='notifications.policy.filter_new_accounts_title' defaultMessage='New accounts' /></strong>
<span className='hint'><FormattedMessage id='notifications.policy.filter_new_accounts.hint' defaultMessage='Created within the past {days, plural, one {one day} other {# days}}' values={{ days: 30 }} /></span> <span className='hint'><FormattedMessage id='notifications.policy.filter_new_accounts.hint' defaultMessage='Created within the past {days, plural, one {one day} other {# days}}' values={{ days: 30 }} /></span>
</CheckboxWithLabel> </CheckboxWithLabel>
<CheckboxWithLabel checked={notificationPolicy.get('filter_private_mentions')} onChange={this.handleFilterPrivateMentions}> <CheckboxWithLabel checked={notificationPolicy.filter_private_mentions} onChange={this.handleFilterPrivateMentions}>
<strong><FormattedMessage id='notifications.policy.filter_private_mentions_title' defaultMessage='Unsolicited private mentions' /></strong> <strong><FormattedMessage id='notifications.policy.filter_private_mentions_title' defaultMessage='Unsolicited private mentions' /></strong>
<span className='hint'><FormattedMessage id='notifications.policy.filter_private_mentions_hint' defaultMessage="Filtered unless it's in reply to your own mention or if you follow the sender" /></span> <span className='hint'><FormattedMessage id='notifications.policy.filter_private_mentions_hint' defaultMessage="Filtered unless it's in reply to your own mention or if you follow the sender" /></span>
</CheckboxWithLabel> </CheckboxWithLabel>

View File

@ -1,49 +0,0 @@
import { useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import InventoryIcon from '@/material-icons/400-24px/inventory_2.svg?react';
import { fetchNotificationPolicy } from 'flavours/glitch/actions/notifications';
import { Icon } from 'flavours/glitch/components/icon';
import { toCappedNumber } from 'flavours/glitch/utils/numbers';
export const FilteredNotificationsBanner = () => {
const dispatch = useDispatch();
const policy = useSelector(state => state.get('notificationPolicy'));
useEffect(() => {
dispatch(fetchNotificationPolicy());
const interval = setInterval(() => {
dispatch(fetchNotificationPolicy());
}, 120000);
return () => {
clearInterval(interval);
};
}, [dispatch]);
if (policy === null || policy.getIn(['summary', 'pending_notifications_count']) === 0) {
return null;
}
return (
<Link className='filtered-notifications-banner' to='/notifications/requests'>
<Icon icon={InventoryIcon} />
<div className='filtered-notifications-banner__text'>
<strong><FormattedMessage id='filtered_notifications_banner.title' defaultMessage='Filtered notifications' /></strong>
<span><FormattedMessage id='filtered_notifications_banner.pending_requests' defaultMessage='Notifications from {count, plural, =0 {no one} one {one person} other {# people}} you may know' values={{ count: policy.getIn(['summary', 'pending_requests_count']) }} /></span>
</div>
<div className='filtered-notifications-banner__badge'>
<div className='filtered-notifications-banner__badge__badge'>{toCappedNumber(policy.getIn(['summary', 'pending_notifications_count']))}</div>
<FormattedMessage id='filtered_notifications_banner.mentions' defaultMessage='{count, plural, one {mention} other {mentions}}' values={{ count: policy.getIn(['summary', 'pending_notifications_count']) }} />
</div>
</Link>
);
};

View File

@ -0,0 +1,68 @@
import { useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import InventoryIcon from '@/material-icons/400-24px/inventory_2.svg?react';
import { fetchNotificationPolicy } from 'flavours/glitch/actions/notification_policies';
import { Icon } from 'flavours/glitch/components/icon';
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
import { toCappedNumber } from 'flavours/glitch/utils/numbers';
export const FilteredNotificationsBanner: React.FC = () => {
const dispatch = useAppDispatch();
const policy = useAppSelector((state) => state.notificationPolicy);
useEffect(() => {
void dispatch(fetchNotificationPolicy());
const interval = setInterval(() => {
void dispatch(fetchNotificationPolicy());
}, 120000);
return () => {
clearInterval(interval);
};
}, [dispatch]);
if (policy === null || policy.summary.pending_notifications_count === 0) {
return null;
}
return (
<Link
className='filtered-notifications-banner'
to='/notifications/requests'
>
<Icon icon={InventoryIcon} id='filtered-notifications' />
<div className='filtered-notifications-banner__text'>
<strong>
<FormattedMessage
id='filtered_notifications_banner.title'
defaultMessage='Filtered notifications'
/>
</strong>
<span>
<FormattedMessage
id='filtered_notifications_banner.pending_requests'
defaultMessage='Notifications from {count, plural, =0 {no one} one {one person} other {# people}} you may know'
values={{ count: policy.summary.pending_requests_count }}
/>
</span>
</div>
<div className='filtered-notifications-banner__badge'>
<div className='filtered-notifications-banner__badge__badge'>
{toCappedNumber(policy.summary.pending_notifications_count)}
</div>
<FormattedMessage
id='filtered_notifications_banner.mentions'
defaultMessage='{count, plural, one {mention} other {mentions}}'
values={{ count: policy.summary.pending_notifications_count }}
/>
</div>
</Link>
);
};

View File

@ -4,7 +4,8 @@ import { connect } from 'react-redux';
import { showAlert } from '../../../actions/alerts'; import { showAlert } from '../../../actions/alerts';
import { openModal } from '../../../actions/modal'; import { openModal } from '../../../actions/modal';
import { setFilter, clearNotifications, requestBrowserPermission, updateNotificationsPolicy } from '../../../actions/notifications'; import { updateNotificationsPolicy } from '../../../actions/notification_policies';
import { setFilter, clearNotifications, requestBrowserPermission } from '../../../actions/notifications';
import { changeAlerts as changePushNotifications } from '../../../actions/push_notifications'; import { changeAlerts as changePushNotifications } from '../../../actions/push_notifications';
import { changeSetting } from '../../../actions/settings'; import { changeSetting } from '../../../actions/settings';
import ColumnSettings from '../components/column_settings'; import ColumnSettings from '../components/column_settings';
@ -15,13 +16,16 @@ const messages = defineMessages({
permissionDenied: { id: 'notifications.permission_denied_alert', defaultMessage: 'Desktop notifications can\'t be enabled, as browser permission has been denied before' }, permissionDenied: { id: 'notifications.permission_denied_alert', defaultMessage: 'Desktop notifications can\'t be enabled, as browser permission has been denied before' },
}); });
/**
* @param {import('flavours/glitch/store').RootState} state
*/
const mapStateToProps = state => ({ const mapStateToProps = state => ({
settings: state.getIn(['settings', 'notifications']), settings: state.getIn(['settings', 'notifications']),
pushSettings: state.get('push_notifications'), pushSettings: state.get('push_notifications'),
alertsEnabled: state.getIn(['settings', 'notifications', 'alerts']).includes(true), alertsEnabled: state.getIn(['settings', 'notifications', 'alerts']).includes(true),
browserSupport: state.getIn(['notifications', 'browserSupport']), browserSupport: state.getIn(['notifications', 'browserSupport']),
browserPermission: state.getIn(['notifications', 'browserPermission']), browserPermission: state.getIn(['notifications', 'browserPermission']),
notificationPolicy: state.get('notificationPolicy'), notificationPolicy: state.notificationPolicy,
}); });
const mapDispatchToProps = (dispatch, { intl }) => ({ const mapDispatchToProps = (dispatch, { intl }) => ({

View File

@ -0,0 +1,3 @@
import type { NotificationPolicyJSON } from 'flavours/glitch/api_types/notification_policies';
export type NotificationPolicy = NotificationPolicyJSON; // No changes from the API type

View File

@ -1,12 +0,0 @@
import { fromJS } from 'immutable';
import { NOTIFICATION_POLICY_FETCH_SUCCESS } from 'flavours/glitch/actions/notifications';
export const notificationPolicyReducer = (state = null, action) => {
switch(action.type) {
case NOTIFICATION_POLICY_FETCH_SUCCESS:
return fromJS(action.policy);
default:
return state;
}
};

View File

@ -0,0 +1,18 @@
import { createReducer, isAnyOf } from '@reduxjs/toolkit';
import {
fetchNotificationPolicy,
updateNotificationsPolicy,
} from 'flavours/glitch/actions/notification_policies';
import type { NotificationPolicy } from 'flavours/glitch/models/notification_policy';
export const notificationPolicyReducer =
createReducer<NotificationPolicy | null>(null, (builder) => {
builder.addMatcher(
isAnyOf(
fetchNotificationPolicy.fulfilled,
updateNotificationsPolicy.fulfilled,
),
(_state, action) => action.payload,
);
});

View File

@ -0,0 +1,16 @@
import {
apiGetNotificationPolicy,
apiUpdateNotificationsPolicy,
} from 'mastodon/api/notification_policies';
import type { NotificationPolicy } from 'mastodon/models/notification_policy';
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
export const fetchNotificationPolicy = createDataLoadingThunk(
'notificationPolicy/fetch',
() => apiGetNotificationPolicy(),
);
export const updateNotificationsPolicy = createDataLoadingThunk(
'notificationPolicy/update',
(policy: Partial<NotificationPolicy>) => apiUpdateNotificationsPolicy(policy),
);

View File

@ -44,10 +44,6 @@ export const NOTIFICATIONS_MARK_AS_READ = 'NOTIFICATIONS_MARK_AS_READ';
export const NOTIFICATIONS_SET_BROWSER_SUPPORT = 'NOTIFICATIONS_SET_BROWSER_SUPPORT'; export const NOTIFICATIONS_SET_BROWSER_SUPPORT = 'NOTIFICATIONS_SET_BROWSER_SUPPORT';
export const NOTIFICATIONS_SET_BROWSER_PERMISSION = 'NOTIFICATIONS_SET_BROWSER_PERMISSION'; export const NOTIFICATIONS_SET_BROWSER_PERMISSION = 'NOTIFICATIONS_SET_BROWSER_PERMISSION';
export const NOTIFICATION_POLICY_FETCH_REQUEST = 'NOTIFICATION_POLICY_FETCH_REQUEST';
export const NOTIFICATION_POLICY_FETCH_SUCCESS = 'NOTIFICATION_POLICY_FETCH_SUCCESS';
export const NOTIFICATION_POLICY_FETCH_FAIL = 'NOTIFICATION_POLICY_FETCH_FAIL';
export const NOTIFICATION_REQUESTS_FETCH_REQUEST = 'NOTIFICATION_REQUESTS_FETCH_REQUEST'; export const NOTIFICATION_REQUESTS_FETCH_REQUEST = 'NOTIFICATION_REQUESTS_FETCH_REQUEST';
export const NOTIFICATION_REQUESTS_FETCH_SUCCESS = 'NOTIFICATION_REQUESTS_FETCH_SUCCESS'; export const NOTIFICATION_REQUESTS_FETCH_SUCCESS = 'NOTIFICATION_REQUESTS_FETCH_SUCCESS';
export const NOTIFICATION_REQUESTS_FETCH_FAIL = 'NOTIFICATION_REQUESTS_FETCH_FAIL'; export const NOTIFICATION_REQUESTS_FETCH_FAIL = 'NOTIFICATION_REQUESTS_FETCH_FAIL';
@ -346,40 +342,6 @@ export function setBrowserPermission (value) {
}; };
} }
export const fetchNotificationPolicy = () => (dispatch) => {
dispatch(fetchNotificationPolicyRequest());
api().get('/api/v1/notifications/policy').then(({ data }) => {
dispatch(fetchNotificationPolicySuccess(data));
}).catch(err => {
dispatch(fetchNotificationPolicyFail(err));
});
};
export const fetchNotificationPolicyRequest = () => ({
type: NOTIFICATION_POLICY_FETCH_REQUEST,
});
export const fetchNotificationPolicySuccess = policy => ({
type: NOTIFICATION_POLICY_FETCH_SUCCESS,
policy,
});
export const fetchNotificationPolicyFail = error => ({
type: NOTIFICATION_POLICY_FETCH_FAIL,
error,
});
export const updateNotificationsPolicy = params => (dispatch) => {
dispatch(fetchNotificationPolicyRequest());
api().put('/api/v1/notifications/policy', params).then(({ data }) => {
dispatch(fetchNotificationPolicySuccess(data));
}).catch(err => {
dispatch(fetchNotificationPolicyFail(err));
});
};
export const fetchNotificationRequests = () => (dispatch, getState) => { export const fetchNotificationRequests = () => (dispatch, getState) => {
const params = {}; const params = {};

View File

@ -0,0 +1,10 @@
import { apiRequest } from 'mastodon/api';
import type { NotificationPolicyJSON } from 'mastodon/api_types/notification_policies';
export const apiGetNotificationPolicy = () =>
apiRequest<NotificationPolicyJSON>('GET', '/v1/notifications/policy');
export const apiUpdateNotificationsPolicy = (
policy: Partial<NotificationPolicyJSON>,
) =>
apiRequest<NotificationPolicyJSON>('PUT', '/v1/notifications/policy', policy);

View File

@ -0,0 +1,12 @@
// See app/serializers/rest/notification_policy_serializer.rb
export interface NotificationPolicyJSON {
filter_not_following: boolean;
filter_not_followers: boolean;
filter_new_accounts: boolean;
filter_private_mentions: boolean;
summary: {
pending_requests_count: number;
pending_notifications_count: number;
};
}

View File

@ -24,7 +24,7 @@ class ColumnSettings extends PureComponent {
alertsEnabled: PropTypes.bool, alertsEnabled: PropTypes.bool,
browserSupport: PropTypes.bool, browserSupport: PropTypes.bool,
browserPermission: PropTypes.string, browserPermission: PropTypes.string,
notificationPolicy: ImmutablePropTypes.map, notificationPolicy: PropTypes.object.isRequired,
onChangePolicy: PropTypes.func.isRequired, onChangePolicy: PropTypes.func.isRequired,
}; };
@ -82,22 +82,22 @@ class ColumnSettings extends PureComponent {
<h3><FormattedMessage id='notifications.policy.title' defaultMessage='Filter out notifications from…' /></h3> <h3><FormattedMessage id='notifications.policy.title' defaultMessage='Filter out notifications from…' /></h3>
<div className='column-settings__row'> <div className='column-settings__row'>
<CheckboxWithLabel checked={notificationPolicy.get('filter_not_following')} onChange={this.handleFilterNotFollowing}> <CheckboxWithLabel checked={notificationPolicy.filter_not_following} onChange={this.handleFilterNotFollowing}>
<strong><FormattedMessage id='notifications.policy.filter_not_following_title' defaultMessage="People you don't follow" /></strong> <strong><FormattedMessage id='notifications.policy.filter_not_following_title' defaultMessage="People you don't follow" /></strong>
<span className='hint'><FormattedMessage id='notifications.policy.filter_not_following_hint' defaultMessage='Until you manually approve them' /></span> <span className='hint'><FormattedMessage id='notifications.policy.filter_not_following_hint' defaultMessage='Until you manually approve them' /></span>
</CheckboxWithLabel> </CheckboxWithLabel>
<CheckboxWithLabel checked={notificationPolicy.get('filter_not_followers')} onChange={this.handleFilterNotFollowers}> <CheckboxWithLabel checked={notificationPolicy.filter_not_followers} onChange={this.handleFilterNotFollowers}>
<strong><FormattedMessage id='notifications.policy.filter_not_followers_title' defaultMessage='People not following you' /></strong> <strong><FormattedMessage id='notifications.policy.filter_not_followers_title' defaultMessage='People not following you' /></strong>
<span className='hint'><FormattedMessage id='notifications.policy.filter_not_followers_hint' defaultMessage='Including people who have been following you fewer than {days, plural, one {one day} other {# days}}' values={{ days: 3 }} /></span> <span className='hint'><FormattedMessage id='notifications.policy.filter_not_followers_hint' defaultMessage='Including people who have been following you fewer than {days, plural, one {one day} other {# days}}' values={{ days: 3 }} /></span>
</CheckboxWithLabel> </CheckboxWithLabel>
<CheckboxWithLabel checked={notificationPolicy.get('filter_new_accounts')} onChange={this.handleFilterNewAccounts}> <CheckboxWithLabel checked={notificationPolicy.filter_new_accounts} onChange={this.handleFilterNewAccounts}>
<strong><FormattedMessage id='notifications.policy.filter_new_accounts_title' defaultMessage='New accounts' /></strong> <strong><FormattedMessage id='notifications.policy.filter_new_accounts_title' defaultMessage='New accounts' /></strong>
<span className='hint'><FormattedMessage id='notifications.policy.filter_new_accounts.hint' defaultMessage='Created within the past {days, plural, one {one day} other {# days}}' values={{ days: 30 }} /></span> <span className='hint'><FormattedMessage id='notifications.policy.filter_new_accounts.hint' defaultMessage='Created within the past {days, plural, one {one day} other {# days}}' values={{ days: 30 }} /></span>
</CheckboxWithLabel> </CheckboxWithLabel>
<CheckboxWithLabel checked={notificationPolicy.get('filter_private_mentions')} onChange={this.handleFilterPrivateMentions}> <CheckboxWithLabel checked={notificationPolicy.filter_private_mentions} onChange={this.handleFilterPrivateMentions}>
<strong><FormattedMessage id='notifications.policy.filter_private_mentions_title' defaultMessage='Unsolicited private mentions' /></strong> <strong><FormattedMessage id='notifications.policy.filter_private_mentions_title' defaultMessage='Unsolicited private mentions' /></strong>
<span className='hint'><FormattedMessage id='notifications.policy.filter_private_mentions_hint' defaultMessage="Filtered unless it's in reply to your own mention or if you follow the sender" /></span> <span className='hint'><FormattedMessage id='notifications.policy.filter_private_mentions_hint' defaultMessage="Filtered unless it's in reply to your own mention or if you follow the sender" /></span>
</CheckboxWithLabel> </CheckboxWithLabel>

View File

@ -1,49 +0,0 @@
import { useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import InventoryIcon from '@/material-icons/400-24px/inventory_2.svg?react';
import { fetchNotificationPolicy } from 'mastodon/actions/notifications';
import { Icon } from 'mastodon/components/icon';
import { toCappedNumber } from 'mastodon/utils/numbers';
export const FilteredNotificationsBanner = () => {
const dispatch = useDispatch();
const policy = useSelector(state => state.get('notificationPolicy'));
useEffect(() => {
dispatch(fetchNotificationPolicy());
const interval = setInterval(() => {
dispatch(fetchNotificationPolicy());
}, 120000);
return () => {
clearInterval(interval);
};
}, [dispatch]);
if (policy === null || policy.getIn(['summary', 'pending_notifications_count']) === 0) {
return null;
}
return (
<Link className='filtered-notifications-banner' to='/notifications/requests'>
<Icon icon={InventoryIcon} />
<div className='filtered-notifications-banner__text'>
<strong><FormattedMessage id='filtered_notifications_banner.title' defaultMessage='Filtered notifications' /></strong>
<span><FormattedMessage id='filtered_notifications_banner.pending_requests' defaultMessage='Notifications from {count, plural, =0 {no one} one {one person} other {# people}} you may know' values={{ count: policy.getIn(['summary', 'pending_requests_count']) }} /></span>
</div>
<div className='filtered-notifications-banner__badge'>
<div className='filtered-notifications-banner__badge__badge'>{toCappedNumber(policy.getIn(['summary', 'pending_notifications_count']))}</div>
<FormattedMessage id='filtered_notifications_banner.mentions' defaultMessage='{count, plural, one {mention} other {mentions}}' values={{ count: policy.getIn(['summary', 'pending_notifications_count']) }} />
</div>
</Link>
);
};

View File

@ -0,0 +1,68 @@
import { useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import InventoryIcon from '@/material-icons/400-24px/inventory_2.svg?react';
import { fetchNotificationPolicy } from 'mastodon/actions/notification_policies';
import { Icon } from 'mastodon/components/icon';
import { useAppSelector, useAppDispatch } from 'mastodon/store';
import { toCappedNumber } from 'mastodon/utils/numbers';
export const FilteredNotificationsBanner: React.FC = () => {
const dispatch = useAppDispatch();
const policy = useAppSelector((state) => state.notificationPolicy);
useEffect(() => {
void dispatch(fetchNotificationPolicy());
const interval = setInterval(() => {
void dispatch(fetchNotificationPolicy());
}, 120000);
return () => {
clearInterval(interval);
};
}, [dispatch]);
if (policy === null || policy.summary.pending_notifications_count === 0) {
return null;
}
return (
<Link
className='filtered-notifications-banner'
to='/notifications/requests'
>
<Icon icon={InventoryIcon} id='filtered-notifications' />
<div className='filtered-notifications-banner__text'>
<strong>
<FormattedMessage
id='filtered_notifications_banner.title'
defaultMessage='Filtered notifications'
/>
</strong>
<span>
<FormattedMessage
id='filtered_notifications_banner.pending_requests'
defaultMessage='Notifications from {count, plural, =0 {no one} one {one person} other {# people}} you may know'
values={{ count: policy.summary.pending_requests_count }}
/>
</span>
</div>
<div className='filtered-notifications-banner__badge'>
<div className='filtered-notifications-banner__badge__badge'>
{toCappedNumber(policy.summary.pending_notifications_count)}
</div>
<FormattedMessage
id='filtered_notifications_banner.mentions'
defaultMessage='{count, plural, one {mention} other {mentions}}'
values={{ count: policy.summary.pending_notifications_count }}
/>
</div>
</Link>
);
};

View File

@ -4,7 +4,8 @@ import { connect } from 'react-redux';
import { showAlert } from '../../../actions/alerts'; import { showAlert } from '../../../actions/alerts';
import { openModal } from '../../../actions/modal'; import { openModal } from '../../../actions/modal';
import { setFilter, clearNotifications, requestBrowserPermission, updateNotificationsPolicy } from '../../../actions/notifications'; import { updateNotificationsPolicy } from '../../../actions/notification_policies';
import { setFilter, clearNotifications, requestBrowserPermission } from '../../../actions/notifications';
import { changeAlerts as changePushNotifications } from '../../../actions/push_notifications'; import { changeAlerts as changePushNotifications } from '../../../actions/push_notifications';
import { changeSetting } from '../../../actions/settings'; import { changeSetting } from '../../../actions/settings';
import ColumnSettings from '../components/column_settings'; import ColumnSettings from '../components/column_settings';
@ -15,13 +16,16 @@ const messages = defineMessages({
permissionDenied: { id: 'notifications.permission_denied_alert', defaultMessage: 'Desktop notifications can\'t be enabled, as browser permission has been denied before' }, permissionDenied: { id: 'notifications.permission_denied_alert', defaultMessage: 'Desktop notifications can\'t be enabled, as browser permission has been denied before' },
}); });
/**
* @param {import('mastodon/store').RootState} state
*/
const mapStateToProps = state => ({ const mapStateToProps = state => ({
settings: state.getIn(['settings', 'notifications']), settings: state.getIn(['settings', 'notifications']),
pushSettings: state.get('push_notifications'), pushSettings: state.get('push_notifications'),
alertsEnabled: state.getIn(['settings', 'notifications', 'alerts']).includes(true), alertsEnabled: state.getIn(['settings', 'notifications', 'alerts']).includes(true),
browserSupport: state.getIn(['notifications', 'browserSupport']), browserSupport: state.getIn(['notifications', 'browserSupport']),
browserPermission: state.getIn(['notifications', 'browserPermission']), browserPermission: state.getIn(['notifications', 'browserPermission']),
notificationPolicy: state.get('notificationPolicy'), notificationPolicy: state.notificationPolicy,
}); });
const mapDispatchToProps = (dispatch, { intl }) => ({ const mapDispatchToProps = (dispatch, { intl }) => ({

View File

@ -147,14 +147,14 @@
"compose.published.body": "Postitus avaldatud.", "compose.published.body": "Postitus avaldatud.",
"compose.published.open": "Ava", "compose.published.open": "Ava",
"compose.saved.body": "Postitus salvestatud.", "compose.saved.body": "Postitus salvestatud.",
"compose_form.direct_message_warning_learn_more": "Vaata täpsemalt", "compose_form.direct_message_warning_learn_more": "Vaata lisa",
"compose_form.encryption_warning": "Postitused Mastodonis ei ole otsast-otsani krüpteeritud. Ära jaga mingeid delikaatseid andmeid Mastodoni kaudu.", "compose_form.encryption_warning": "Postitused Mastodonis ei ole otsast-otsani krüpteeritud. Ära jaga mingeid delikaatseid andmeid Mastodoni kaudu.",
"compose_form.hashtag_warning": "See postitus ei ilmu ühegi märksõna all, kuna pole avalik. Vaid avalikud postitused on märksõnade kaudu leitavad.", "compose_form.hashtag_warning": "See postitus ei ilmu ühegi märksõna all, kuna pole avalik. Vaid avalikud postitused on märksõnade kaudu leitavad.",
"compose_form.lock_disclaimer": "Su konto ei ole {locked}. Igaüks saab sind jälgida, et näha su ainult-jälgijatele postitusi.", "compose_form.lock_disclaimer": "Su konto ei ole {locked}. Igaüks saab sind jälgida, et näha su ainult-jälgijatele postitusi.",
"compose_form.lock_disclaimer.lock": "lukus", "compose_form.lock_disclaimer.lock": "lukus",
"compose_form.placeholder": "Millest mõtled?", "compose_form.placeholder": "Millest mõtled?",
"compose_form.poll.duration": "Küsitluse kestus", "compose_form.poll.duration": "Küsitluse kestus",
"compose_form.poll.multiple": "Valikvastustega", "compose_form.poll.multiple": "Mitu vastust",
"compose_form.poll.option_placeholder": "Valik {number}", "compose_form.poll.option_placeholder": "Valik {number}",
"compose_form.poll.single": "Vali üks", "compose_form.poll.single": "Vali üks",
"compose_form.poll.switch_to_multiple": "Muuda küsitlust mitmikvaliku lubamiseks", "compose_form.poll.switch_to_multiple": "Muuda küsitlust mitmikvaliku lubamiseks",
@ -297,6 +297,7 @@
"filter_modal.select_filter.subtitle": "Kasuta olemasolevat kategooriat või loo uus", "filter_modal.select_filter.subtitle": "Kasuta olemasolevat kategooriat või loo uus",
"filter_modal.select_filter.title": "Filtreeri seda postitust", "filter_modal.select_filter.title": "Filtreeri seda postitust",
"filter_modal.title.status": "Postituse filtreerimine", "filter_modal.title.status": "Postituse filtreerimine",
"filtered_notifications_banner.mentions": "{count, plural, one {mainimine} other {mainimist}}",
"filtered_notifications_banner.pending_requests": "Teateid {count, plural, =0 {mitte üheltki} one {ühelt} other {#}} inimeselt, keda võid teada", "filtered_notifications_banner.pending_requests": "Teateid {count, plural, =0 {mitte üheltki} one {ühelt} other {#}} inimeselt, keda võid teada",
"filtered_notifications_banner.title": "Filtreeritud teavitused", "filtered_notifications_banner.title": "Filtreeritud teavitused",
"firehose.all": "Kõik", "firehose.all": "Kõik",
@ -305,15 +306,19 @@
"follow_request.authorize": "Autoriseeri", "follow_request.authorize": "Autoriseeri",
"follow_request.reject": "Hülga", "follow_request.reject": "Hülga",
"follow_requests.unlocked_explanation": "Kuigi su konto pole lukustatud, soovitab {domain} personal siiski nende kontode jälgimistaotlused käsitsi üle vaadata.", "follow_requests.unlocked_explanation": "Kuigi su konto pole lukustatud, soovitab {domain} personal siiski nende kontode jälgimistaotlused käsitsi üle vaadata.",
"follow_suggestions.curated_suggestion": "Teiste valitud", "follow_suggestions.curated_suggestion": "Meeskonna valitud",
"follow_suggestions.dismiss": "Ära enam näita", "follow_suggestions.dismiss": "Ära enam näita",
"follow_suggestions.hints.featured": "Selle kasutajaprofiili on soovitanud {domain} kasutajad.", "follow_suggestions.featured_longer": "Käsitsi valitud {domain} meeskonna poolt",
"follow_suggestions.hints.friends_of_friends": "See kasutajaprofiil on jälgitavate seas populaarne.", "follow_suggestions.friends_of_friends_longer": "Populaarne inimeste hulgas, keda jälgid",
"follow_suggestions.hints.featured": "Selle kasutajaprofiili on soovitanud {domain} meeskond.",
"follow_suggestions.hints.friends_of_friends": "See kasutajaprofiil on sinu jälgitavate seas populaarne.",
"follow_suggestions.hints.most_followed": "See on {domain} enim jälgitud kasutajaprofiil.", "follow_suggestions.hints.most_followed": "See on {domain} enim jälgitud kasutajaprofiil.",
"follow_suggestions.hints.most_interactions": "See on {domain} viimasel ajal enim tähelepanu saanud kasutajaprofiil.", "follow_suggestions.hints.most_interactions": "See kasutajaprofiil on viimasel ajal {domain} saanud palju tähelepanu.",
"follow_suggestions.hints.similar_to_recently_followed": "See kasutajaprofiil sarnaneb neile, mida oled hiljuti jälgima asunud.", "follow_suggestions.hints.similar_to_recently_followed": "See kasutajaprofiil sarnaneb neile, mida oled hiljuti jälgima asunud.",
"follow_suggestions.personalized_suggestion": "Isikupärastatud soovitus", "follow_suggestions.personalized_suggestion": "Isikupärastatud soovitus",
"follow_suggestions.popular_suggestion": "Popuplaarne soovitus", "follow_suggestions.popular_suggestion": "Popuplaarne soovitus",
"follow_suggestions.popular_suggestion_longer": "Populaarne kohas {domain}",
"follow_suggestions.similar_to_recently_followed_longer": "Sarnane profiilile, mida hiljuti jälgima hakkasid",
"follow_suggestions.view_all": "Vaata kõiki", "follow_suggestions.view_all": "Vaata kõiki",
"follow_suggestions.who_to_follow": "Keda jälgida", "follow_suggestions.who_to_follow": "Keda jälgida",
"followed_tags": "Jälgitavad märksõnad", "followed_tags": "Jälgitavad märksõnad",
@ -409,6 +414,8 @@
"limited_account_hint.action": "Näita profilli sellegipoolest", "limited_account_hint.action": "Näita profilli sellegipoolest",
"limited_account_hint.title": "See profiil on peidetud {domain} moderaatorite poolt.", "limited_account_hint.title": "See profiil on peidetud {domain} moderaatorite poolt.",
"link_preview.author": "{name} poolt", "link_preview.author": "{name} poolt",
"link_preview.more_from_author": "Veel kasutajalt {name}",
"link_preview.shares": "{count, plural, one {{counter} postitus} other {{counter} postitust}}",
"lists.account.add": "Lisa nimekirja", "lists.account.add": "Lisa nimekirja",
"lists.account.remove": "Eemalda nimekirjast", "lists.account.remove": "Eemalda nimekirjast",
"lists.delete": "Kustuta nimekiri", "lists.delete": "Kustuta nimekiri",
@ -468,13 +475,22 @@
"notification.follow": "{name} alustas su jälgimist", "notification.follow": "{name} alustas su jälgimist",
"notification.follow_request": "{name} soovib sind jälgida", "notification.follow_request": "{name} soovib sind jälgida",
"notification.mention": "{name} mainis sind", "notification.mention": "{name} mainis sind",
"notification.moderation-warning.learn_more": "Vaata lisa",
"notification.moderation_warning": "Said modereerimise hoiatuse",
"notification.moderation_warning.action_delete_statuses": "Mõni su postitus on eemaldatud.",
"notification.moderation_warning.action_disable": "Su konto on keelatud.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Mõni su postitustest on märgitud kui tundlik.",
"notification.moderation_warning.action_none": "Su konto on saanud modereerimise hoiatuse.",
"notification.moderation_warning.action_sensitive": "Su postitused märgitakse nüüdsest tundlikuks.",
"notification.moderation_warning.action_silence": "Su kontole pandi piirang.",
"notification.moderation_warning.action_suspend": "Su konto on peatatud.",
"notification.own_poll": "Su küsitlus on lõppenud", "notification.own_poll": "Su küsitlus on lõppenud",
"notification.poll": "Küsitlus, milles osalesid, on lõppenud", "notification.poll": "Küsitlus, milles osalesid, on lõppenud",
"notification.reblog": "{name} jagas edasi postitust", "notification.reblog": "{name} jagas edasi postitust",
"notification.relationships_severance_event": "Kadunud ühendus kasutajaga {name}", "notification.relationships_severance_event": "Kadunud ühendus kasutajaga {name}",
"notification.relationships_severance_event.account_suspension": "{from} admin on kustutanud {target}, mis tähendab, et sa ei saa enam neilt uuendusi või suhelda nendega.", "notification.relationships_severance_event.account_suspension": "{from} admin on kustutanud {target}, mis tähendab, et sa ei saa enam neilt uuendusi või suhelda nendega.",
"notification.relationships_severance_event.domain_block": "{from} admin on blokeerinud {target}, sealhulgas {followersCount} sinu jälgijat ja {followingCount, plural, one {# konto} other {# kontot}}, mida jälgid.", "notification.relationships_severance_event.domain_block": "{from} admin on blokeerinud {target}, sealhulgas {followersCount} sinu jälgijat ja {followingCount, plural, one {# konto} other {# kontot}}, mida jälgid.",
"notification.relationships_severance_event.learn_more": "Saa rohkem teada", "notification.relationships_severance_event.learn_more": "Vaata lisa",
"notification.relationships_severance_event.user_domain_block": "Blokeerisid {target}, eemaldades oma jälgijate hulgast {followersCount} ja jälgitavate hulgast {followingCount, plural, one {# konto} other {# kontot}}.", "notification.relationships_severance_event.user_domain_block": "Blokeerisid {target}, eemaldades oma jälgijate hulgast {followersCount} ja jälgitavate hulgast {followingCount, plural, one {# konto} other {# kontot}}.",
"notification.status": "{name} just postitas", "notification.status": "{name} just postitas",
"notification.update": "{name} muutis postitust", "notification.update": "{name} muutis postitust",
@ -680,8 +696,11 @@
"server_banner.about_active_users": "Inimesed, kes kasutavad seda serverit viimase 30 päeva jooksul (kuu aktiivsed kasutajad)", "server_banner.about_active_users": "Inimesed, kes kasutavad seda serverit viimase 30 päeva jooksul (kuu aktiivsed kasutajad)",
"server_banner.active_users": "aktiivsed kasutajad", "server_banner.active_users": "aktiivsed kasutajad",
"server_banner.administered_by": "Administraator:", "server_banner.administered_by": "Administraator:",
"server_banner.is_one_of_many": "{domain} on üks paljudest sõltumatutest Mastodoni serveritest, mida saab fediversumis osalemiseks kasutada.",
"server_banner.server_stats": "Serveri statistika:", "server_banner.server_stats": "Serveri statistika:",
"sign_in_banner.create_account": "Loo konto", "sign_in_banner.create_account": "Loo konto",
"sign_in_banner.follow_anyone": "Jälgi ükskõik keda kogu fediversumist ja näe kõike ajalises järjestuses. Ei mingeid algoritme, reklaame või klikipüüdjaid segamas.",
"sign_in_banner.mastodon_is": "Mastodon on parim viis olemaks kursis sellega, mis toimub.",
"sign_in_banner.sign_in": "Logi sisse", "sign_in_banner.sign_in": "Logi sisse",
"sign_in_banner.sso_redirect": "Sisene või registreeru", "sign_in_banner.sso_redirect": "Sisene või registreeru",
"status.admin_account": "Ava @{name} moderaatorivaates", "status.admin_account": "Ava @{name} moderaatorivaates",

View File

@ -696,8 +696,10 @@
"server_banner.about_active_users": "Personas que ha usate iste servitor in le ultime 30 dies (usatores active per mense)", "server_banner.about_active_users": "Personas que ha usate iste servitor in le ultime 30 dies (usatores active per mense)",
"server_banner.active_users": "usatores active", "server_banner.active_users": "usatores active",
"server_banner.administered_by": "Administrate per:", "server_banner.administered_by": "Administrate per:",
"server_banner.is_one_of_many": "{domain} es un de multe servitores independente de Mastodon que tu pote usar pro participar in le fediverso.",
"server_banner.server_stats": "Statos del servitor:", "server_banner.server_stats": "Statos del servitor:",
"sign_in_banner.create_account": "Crear un conto", "sign_in_banner.create_account": "Crear un conto",
"sign_in_banner.mastodon_is": "Mastodon es le melior maniera de sequer lo que passa.",
"sign_in_banner.sign_in": "Aperir session", "sign_in_banner.sign_in": "Aperir session",
"sign_in_banner.sso_redirect": "Aperir session o crear conto", "sign_in_banner.sso_redirect": "Aperir session o crear conto",
"status.admin_account": "Aperir le interfacie de moderation pro @{name}", "status.admin_account": "Aperir le interfacie de moderation pro @{name}",

View File

@ -0,0 +1,3 @@
import type { NotificationPolicyJSON } from 'mastodon/api_types/notification_policies';
export type NotificationPolicy = NotificationPolicyJSON; // No changes from the API type

View File

@ -1,12 +0,0 @@
import { fromJS } from 'immutable';
import { NOTIFICATION_POLICY_FETCH_SUCCESS } from 'mastodon/actions/notifications';
export const notificationPolicyReducer = (state = null, action) => {
switch(action.type) {
case NOTIFICATION_POLICY_FETCH_SUCCESS:
return fromJS(action.policy);
default:
return state;
}
};

View File

@ -0,0 +1,18 @@
import { createReducer, isAnyOf } from '@reduxjs/toolkit';
import {
fetchNotificationPolicy,
updateNotificationsPolicy,
} from 'mastodon/actions/notification_policies';
import type { NotificationPolicy } from 'mastodon/models/notification_policy';
export const notificationPolicyReducer =
createReducer<NotificationPolicy | null>(null, (builder) => {
builder.addMatcher(
isAnyOf(
fetchNotificationPolicy.fulfilled,
updateNotificationsPolicy.fulfilled,
),
(_state, action) => action.payload,
);
});

View File

@ -18,7 +18,7 @@ module Admin::Metrics::Measure::QueryHelper
def generated_series_days def generated_series_days
Arel.sql( Arel.sql(
<<~SQL.squish <<~SQL.squish
SELECT generate_series(timestamp :start_at, :end_at, '1 day')::date AS period SELECT generate_series(:start_at::timestamp, :end_at::timestamp, '1 day')::date AS period
SQL SQL
) )
end end

View File

@ -86,10 +86,6 @@ module Extractor
possible_entries possible_entries
end end
def extract_cashtags_with_indices(_text)
[]
end
def extract_extra_uris_with_indices(text) def extract_extra_uris_with_indices(text)
return [] unless text&.index(':') return [] unless text&.index(':')

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class REST::NotificationPolicySerializer < ActiveModel::Serializer class REST::NotificationPolicySerializer < ActiveModel::Serializer
# Please update `app/javascript/mastodon/api_types/notification_policies.ts` when making changes to the attributes
attributes :filter_not_following, attributes :filter_not_following,
:filter_not_followers, :filter_not_followers,
:filter_new_accounts, :filter_new_accounts,

View File

@ -135,6 +135,7 @@ et:
media: Lisatud meedia media: Lisatud meedia
mutes: Vaigistused mutes: Vaigistused
notifications: Teavitused notifications: Teavitused
profile: Sinu Mastodoni profiil
push: Tõuketeated push: Tõuketeated
reports: Teavitused reports: Teavitused
search: Otsing search: Otsing
@ -165,6 +166,7 @@ et:
admin:write:reports: teostada moderaatori tegevusi teavitustel admin:write:reports: teostada moderaatori tegevusi teavitustel
crypto: kasuta otspunktkrüpeerimist crypto: kasuta otspunktkrüpeerimist
follow: muuta kontode suhteid follow: muuta kontode suhteid
profile: loe vaid oma konto profiili infot
push: saab tõuketeateid push: saab tõuketeateid
read: lugeda konto kõiki andmeid read: lugeda konto kõiki andmeid
read:accounts: näha konto informatsiooni read:accounts: näha konto informatsiooni

View File

@ -285,6 +285,7 @@ et:
update_custom_emoji_html: "%{name} uuendas emotikoni %{target}" update_custom_emoji_html: "%{name} uuendas emotikoni %{target}"
update_domain_block_html: "%{name} uuendas domeeni %{target} keeldu" update_domain_block_html: "%{name} uuendas domeeni %{target} keeldu"
update_ip_block_html: "%{name} muutis IP-aadressi %{target} reeglit" update_ip_block_html: "%{name} muutis IP-aadressi %{target} reeglit"
update_report_html: "%{name} uuendas raportit %{target}"
update_status_html: "%{name} muutis %{target} postitust" update_status_html: "%{name} muutis %{target} postitust"
update_user_role_html: "%{name} muutis %{target} rolli" update_user_role_html: "%{name} muutis %{target} rolli"
deleted_account: kustutatud konto deleted_account: kustutatud konto
@ -292,6 +293,7 @@ et:
filter_by_action: Filtreeri tegevuse järgi filter_by_action: Filtreeri tegevuse järgi
filter_by_user: Filtreeri kasutaja järgi filter_by_user: Filtreeri kasutaja järgi
title: Auditilogi title: Auditilogi
unavailable_instance: "(domeeni nimi pole saadaval)"
announcements: announcements:
destroyed_msg: Teadaande kustutamine õnnestus! destroyed_msg: Teadaande kustutamine õnnestus!
edit: edit:
@ -751,6 +753,7 @@ et:
desc_html: See tugineb välistele hCaptcha skriptidele, mis võib olla turvalisuse ja privaatsuse probleem. Lisaks <strong>võib see muuta registreerimisprotsessi mõnede inimeste (eriti puudega inimeste) jaoks, oluliselt vähem ligipääsetavaks.</strong> Neil põhjustel kaalu palun teisi võimalusi, näiteks kinnitamis- või kutsepõhiseid registreerimislahendusi. desc_html: See tugineb välistele hCaptcha skriptidele, mis võib olla turvalisuse ja privaatsuse probleem. Lisaks <strong>võib see muuta registreerimisprotsessi mõnede inimeste (eriti puudega inimeste) jaoks, oluliselt vähem ligipääsetavaks.</strong> Neil põhjustel kaalu palun teisi võimalusi, näiteks kinnitamis- või kutsepõhiseid registreerimislahendusi.
title: Nõua uutelt kasutajatelt konto kinnitamiseks CAPTCHA lahendamist title: Nõua uutelt kasutajatelt konto kinnitamiseks CAPTCHA lahendamist
content_retention: content_retention:
danger_zone: Ohutsoon
preamble: Määra, kuidas kasutajate loodud sisu Mastodonis talletatakse. preamble: Määra, kuidas kasutajate loodud sisu Mastodonis talletatakse.
title: Sisu talletamine title: Sisu talletamine
default_noindex: default_noindex:
@ -770,7 +773,7 @@ et:
disabled: Mitte kellelegi disabled: Mitte kellelegi
users: Sisseloginud kohalikele kasutajatele users: Sisseloginud kohalikele kasutajatele
registrations: registrations:
moderation_recommandation: Enne registreeringute avamist kõigile veendu, et oleks olemas adekvaatne ja reageerimisvalmis modereerijaskond! moderation_recommandation: Enne kõigi jaoks registreerimise avamist veendu, et oleks olemas adekvaatne ja reageerimisvalmis modereerijaskond!
preamble: Kes saab serveril konto luua. preamble: Kes saab serveril konto luua.
title: Registreerimised title: Registreerimised
registrations_mode: registrations_mode:
@ -791,7 +794,7 @@ et:
software_updates: software_updates:
critical_update: Kriitiline — uuenda kiiresti critical_update: Kriitiline — uuenda kiiresti
description: On soovitav hoida oma Mastodoni paigaldus kõige uuemal kujul, et saada kasu kõige värskematest parandustest ja oskustest. Lisaks, vahel on Mastodoni kiire uuendamine kriitiline, et vältida turvaprobleeme. Neil põhjustel kontrollib Mastodon uuendusi iga 30 minuti tagant ja teavitab sind vastavalt su e-posti teavituste eelistustele. description: On soovitav hoida oma Mastodoni paigaldus kõige uuemal kujul, et saada kasu kõige värskematest parandustest ja oskustest. Lisaks, vahel on Mastodoni kiire uuendamine kriitiline, et vältida turvaprobleeme. Neil põhjustel kontrollib Mastodon uuendusi iga 30 minuti tagant ja teavitab sind vastavalt su e-posti teavituste eelistustele.
documentation_link: Saa rohkem teada documentation_link: Vaata lisa
release_notes: Väljalaskemärkused release_notes: Väljalaskemärkused
title: Saadaval uuendused title: Saadaval uuendused
type: Tüüp type: Tüüp
@ -949,6 +952,7 @@ et:
delete: Kustuta delete: Kustuta
edit_preset: Hoiatuse eelseadistuse muutmine edit_preset: Hoiatuse eelseadistuse muutmine
empty: Hoiatuste eelseadeid pole defineeritud. empty: Hoiatuste eelseadeid pole defineeritud.
title: Hoiatuste eelhäälestused
webhooks: webhooks:
add_new: Lisa lõpp-punkt add_new: Lisa lõpp-punkt
delete: Kustuta delete: Kustuta
@ -1814,8 +1818,8 @@ et:
title: Arhiivi väljavõte title: Arhiivi väljavõte
failed_2fa: failed_2fa:
details: 'Sisenemise üksikasjad:' details: 'Sisenemise üksikasjad:'
explanation: Keegi püüdis Su kontole siseneda, ent sisestas vale teisese autentimisfaktori. explanation: Keegi püüdis Su kontole siseneda, ent sisestas vale kaheastmelise autentimise faktori.
further_actions_html: Kui see polnud Sina, siis soovitame viivitamata %{action}, kuna see võib olla lekkinud. further_actions_html: Kui see polnud Sina, siis soovitame viivitamata %{action}, kuna see võib olla ohus.
subject: Kaheastmelise autentimise nurjumine subject: Kaheastmelise autentimise nurjumine
title: Kaheastmeline autentimine nurjus title: Kaheastmeline autentimine nurjus
suspicious_sign_in: suspicious_sign_in:
@ -1857,9 +1861,9 @@ et:
silence: Konto limiteeritud silence: Konto limiteeritud
suspend: Konto kustutatud suspend: Konto kustutatud
welcome: welcome:
apps_android_action: Google Play poest apps_android_action: Laadi see Google Playst
apps_ios_action: Allalaadimine App Store'ist apps_ios_action: Allalaadimine App Store'ist
apps_step: Meie ametlikud rakendused. apps_step: Laadi meie ametlikud rakendused.
apps_title: Mastodoni rakendused apps_title: Mastodoni rakendused
checklist_subtitle: 'Kuidas sel uudsel sotsiaalmeediarindel pihta hakata:' checklist_subtitle: 'Kuidas sel uudsel sotsiaalmeediarindel pihta hakata:'
checklist_title: Millest alustada checklist_title: Millest alustada
@ -1872,7 +1876,7 @@ et:
feature_audience_title: Kogu enesekindlalt jälgijaid feature_audience_title: Kogu enesekindlalt jälgijaid
feature_control: Tead ise kõige paremini, mida soovid oma koduvoos näha. Ei aega raiskavaid algoritme ega reklaame. Jälgi ühe kasutajakonto kaudu keda iganes mistahes Mastodoni serveris ja näe postitusi ajalises järjestuses, muutes oma nurgakese Internetist rohkem endale meelepärasemaks. feature_control: Tead ise kõige paremini, mida soovid oma koduvoos näha. Ei aega raiskavaid algoritme ega reklaame. Jälgi ühe kasutajakonto kaudu keda iganes mistahes Mastodoni serveris ja näe postitusi ajalises järjestuses, muutes oma nurgakese Internetist rohkem endale meelepärasemaks.
feature_control_title: Säilita oma ajajoone üle kontroll feature_control_title: Säilita oma ajajoone üle kontroll
feature_creativity: Mastodon toetab audiot, video- ja pildipostitusi, liigipääsetavuse kirjeldusi, küsitlusi, sisuhoiatusi, animeeritud avatare, kohandatud emotikone, pisipiltide lõikeeelistusi ja enamatki, et end võrgus väljendada. Kas avaldad kunsti, muusikat või taskuhäälingusaadet, Mastodon on mõeldud Sinu jaoks. feature_creativity: Mastodon toetab audiot, video- ja pildipostitusi, ligipääsetavuse kirjeldusi, küsitlusi, sisuhoiatusi, animeeritud avatare, kohandatud emotikone, pisipiltide lõikeeelistusi ja enamatki, et end võrgus väljendada. Kas avaldad kunsti, muusikat või taskuhäälingut, Mastodon on mõeldud Sinu jaoks.
feature_creativity_title: Võrreldamatu loovus feature_creativity_title: Võrreldamatu loovus
feature_moderation: Mastodon annab otsustusõiguse tagasi Sinu kätte. Igal serveril on oma reeglid ja regulatsioonid, mida hallatakse kohapeal, mitte nagu ülalt-alla korporatiivses sotsiaalmeedias, võimaldades enim paindlikku vastavust erinevate vajadustega gruppide ja inimeste eelistustele. Liitu sobivate reeglitega serveriga, või käivita oma server. feature_moderation: Mastodon annab otsustusõiguse tagasi Sinu kätte. Igal serveril on oma reeglid ja regulatsioonid, mida hallatakse kohapeal, mitte nagu ülalt-alla korporatiivses sotsiaalmeedias, võimaldades enim paindlikku vastavust erinevate vajadustega gruppide ja inimeste eelistustele. Liitu sobivate reeglitega serveriga, või käivita oma server.
feature_moderation_title: Modereerimine, nagu see olema peab feature_moderation_title: Modereerimine, nagu see olema peab

View File

@ -77,10 +77,15 @@ et:
warn: Varja filtreeritud sisu hoiatusega, nimetades filtri pealkirja warn: Varja filtreeritud sisu hoiatusega, nimetades filtri pealkirja
form_admin_settings: form_admin_settings:
activity_api_enabled: Kohalike postituste, aktiivsete kasutajate ja uute registreerumistr arv nädala kaupa grupeeritult activity_api_enabled: Kohalike postituste, aktiivsete kasutajate ja uute registreerumistr arv nädala kaupa grupeeritult
app_icon: WEBP, PNG, GIF või JPG. Asendab mobiilsel seadmel äpi vaikeikooni kohandatud ikooniga.
backups_retention_period: Kasutajatel on võimalus genereerida oma postitustest hiljem allalaaditav arhiiv. Kui määrata positiivne arv, kustutatakse serveri talletusruumist need arhiivid määratud arvu päevade järel automaatselt.
bootstrap_timeline_accounts: Need kasutajad kinnitatakse uute kasutajate jälgimissoovituste esiritta. bootstrap_timeline_accounts: Need kasutajad kinnitatakse uute kasutajate jälgimissoovituste esiritta.
closed_registrations_message: Kuvatakse, kui liitumised pole võimalikud closed_registrations_message: Kuvatakse, kui liitumised pole võimalikud
custom_css: Kohandatud stiile on võimalik kasutada Mastodoni veebiliideses. content_cache_retention_period: Kõik teiste serverite postitused (sealhulgas jagamised ja vastused) kustutatakse pärast määratud arvu päevade möödumist, sõltumata, kuidas kohalik kasutaja on nende postitustega interakteerunud. Hõlmatud on ka postitused, mille kohalik kasutaja on märkinud järjehoidjaks või lemmikuks. Ka eri instantside kasutajate vahelised privaatsed mainimised kaovad ja neid on võimatu taastada. See seadistus on mõeldud eriotstarbeliste instantside jaoks ja rikub paljude kasutajate ootusi, kui seda rakendatakse üldotstarbelise kasutuse puhul.
custom_css: Mastodoni veebiliideses on võimalik kasutada kohandatud stiile.
favicon: WEBP, PNG, GIF või JPG. Asendab Mastodoni vaike- favicon ikooni kohandatud ikooniga.
mascot: Asendab kohandatud veebiliidese illustratsiooni. mascot: Asendab kohandatud veebiliidese illustratsiooni.
media_cache_retention_period: Kaugkasutajate tehtud postituste meediafailid salvestatakse teie serveri vahemällu. Kui see seadistus on seatud positiivsele väärtusele, kustutatakse meediumifailid määratud päevade möödumisel. Kui meediaandmeid küsitakse pärast nende kustutamist, laaditakse need uuesti alla, kui lähtesisu on veel saadaval. Kuna on olemas piirangud, kui tihti tohivad lingikaardid kolmandatelt saitidelt andmeid pärida, on soovitatav määrata väärtuseks vähemalt 14. Vastasel juhul ei uuendata linkide eelvaatekaarte nõudmise korral enne seda aega.
peers_api_enabled: Domeeninimede loetelu, mida see server on Fediversumis kohanud. Mitte mingeid andmeid selle serveri födereerumise kohta antud serverite pole, vaid üksnes info, et sellest serverist ollakse teadlik. Seda kasutavad teenused, mis koguvad üldist födereerumise statistikat. peers_api_enabled: Domeeninimede loetelu, mida see server on Fediversumis kohanud. Mitte mingeid andmeid selle serveri födereerumise kohta antud serverite pole, vaid üksnes info, et sellest serverist ollakse teadlik. Seda kasutavad teenused, mis koguvad üldist födereerumise statistikat.
profile_directory: Kasutajate kataloog kuvab nimekirja kasutajatest, kes on seda lubanud. profile_directory: Kasutajate kataloog kuvab nimekirja kasutajatest, kes on seda lubanud.
require_invite_text: Kui liitumisi on tarvis kinnitada, oleks "Miks soovid liituda?" vastus vajalik require_invite_text: Kui liitumisi on tarvis kinnitada, oleks "Miks soovid liituda?" vastus vajalik
@ -240,6 +245,7 @@ et:
backups_retention_period: Kasutajate arhiivi talletusperiood backups_retention_period: Kasutajate arhiivi talletusperiood
bootstrap_timeline_accounts: Alati soovita neid kontosid uutele kasutajatele bootstrap_timeline_accounts: Alati soovita neid kontosid uutele kasutajatele
closed_registrations_message: Kohandatud teade, kui liitumine pole võimalik closed_registrations_message: Kohandatud teade, kui liitumine pole võimalik
content_cache_retention_period: Kaugsisu säilitamise aeg
custom_css: Kohandatud CSS custom_css: Kohandatud CSS
mascot: Kohandatud maskott (kunagine) mascot: Kohandatud maskott (kunagine)
media_cache_retention_period: Meediapuhvri talletusperiood media_cache_retention_period: Meediapuhvri talletusperiood

View File

@ -1791,8 +1791,8 @@ vi:
suspicious_sign_in: suspicious_sign_in:
change_password: đổi mật khẩu của bạn change_password: đổi mật khẩu của bạn
details: 'Chi tiết thông tin đăng nhập:' details: 'Chi tiết thông tin đăng nhập:'
explanation: Chúng tôi phát hiện lần đăng nhập bất thường tài khoản của bạn từ một địa chỉ IP mới. explanation: Chúng tôi phát hiện tài khoản của bạn đăng nhập bất thường từ một địa chỉ IP mới.
further_actions_html: Nếu đó không phải là bạn, chúng tôi khuyến nghị %{action} lập tức và bật xác minh hai bước để giữ tài khoản được an toàn. further_actions_html: Nếu đây không phải là bạn, hãy %{action} lập tức và bật xác minh hai bước để giữ tài khoản được an toàn.
subject: Đăng nhập tài khoản từ địa chỉ IP mới subject: Đăng nhập tài khoản từ địa chỉ IP mới
title: Lần đăng nhập mới title: Lần đăng nhập mới
warning: warning:

View File

@ -69,10 +69,10 @@ describe Extractor do
end end
end end
describe 'extract_cashtags_with_indices' do describe 'extract_entities_with_indices' do
it 'returns []' do it 'returns empty array when cashtag present' do
text = '$cashtag' text = '$cashtag'
extracted = described_class.extract_cashtags_with_indices(text) extracted = described_class.extract_entities_with_indices(text)
expect(extracted).to eq [] expect(extracted).to eq []
end end
end end

View File

@ -7,11 +7,28 @@ describe 'Admin Measures' do
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:account) { Fabricate(:account) } let(:account) { Fabricate(:account) }
let(:params) do
{
keys: %w(instance_accounts instance_follows instance_followers),
instance_accounts: {
domain: 'mastodon.social',
include_subdomains: true,
},
instance_follows: {
domain: 'mastodon.social',
include_subdomains: true,
},
instance_followers: {
domain: 'mastodon.social',
include_subdomains: true,
},
}
end
describe 'GET /api/v1/admin/measures' do describe 'GET /api/v1/admin/measures' do
context 'when not authorized' do context 'when not authorized' do
it 'returns http forbidden' do it 'returns http forbidden' do
post '/api/v1/admin/measures', params: { account_id: account.id, limit: 2 } post '/api/v1/admin/measures', params: params
expect(response) expect(response)
.to have_http_status(403) .to have_http_status(403)
@ -22,7 +39,7 @@ describe 'Admin Measures' do
let(:scopes) { 'admin:read' } let(:scopes) { 'admin:read' }
it 'returns http success and status json' do it 'returns http success and status json' do
post '/api/v1/admin/measures', params: { account_id: account.id, limit: 2 }, headers: headers post '/api/v1/admin/measures', params: params, headers: headers
expect(response) expect(response)
.to have_http_status(200) .to have_http_status(200)

View File

@ -1,4 +1,4 @@
# syntax=docker/dockerfile:1.7 # syntax=docker/dockerfile:1.8
# Please see https://docs.docker.com/engine/reference/builder for information about # Please see https://docs.docker.com/engine/reference/builder for information about
# the extended buildx capabilities used in this file. # the extended buildx capabilities used in this file.

View File

@ -15379,15 +15379,15 @@ __metadata:
linkType: hard linkType: hard
"sass@npm:^1.62.1": "sass@npm:^1.62.1":
version: 1.77.5 version: 1.77.6
resolution: "sass@npm:1.77.5" resolution: "sass@npm:1.77.6"
dependencies: dependencies:
chokidar: "npm:>=3.0.0 <4.0.0" chokidar: "npm:>=3.0.0 <4.0.0"
immutable: "npm:^4.0.0" immutable: "npm:^4.0.0"
source-map-js: "npm:>=0.6.2 <2.0.0" source-map-js: "npm:>=0.6.2 <2.0.0"
bin: bin:
sass: sass.js sass: sass.js
checksum: 10c0/9da049b0a3fadab419084d6becdf471e107cf6e3c8ac87cabea2feb845afac75e86c99e06ee721a5aa4f6a2d833ec5380137c4e540ab2f760edf1e4eb6139e69 checksum: 10c0/fe5a393c0aa29eda9f83c06be9b94788b61fe8bad0616ee6e3a25d21ab504f430d40c0064fdca89b02b8e426411ae6dcd906c91f2e48c263575c3d392b6daeb1
languageName: node languageName: node
linkType: hard linkType: hard