1
0
This commit is contained in:
Noa Himesaka 2024-10-17 00:30:51 +09:00
commit d45671066c
94 changed files with 902 additions and 675 deletions

View File

@ -21,9 +21,11 @@ jobs:
uses: actions/checkout@v4
- id: version_vars
run: |
echo mastodon_version_metadata=pr-${{ github.event.pull_request.number }}-$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT
echo mastodon_version_metadata=pr-${{ github.event.pull_request.number }}-$(git rev-parse --short ${{github.event.pull_request.head.sha}}) >> $GITHUB_OUTPUT
echo mastodon_short_sha=$(git rev-parse --short ${{github.event.pull_request.head.sha}}) >> $GITHUB_OUTPUT
outputs:
metadata: ${{ steps.version_vars.outputs.mastodon_version_metadata }}
short_sha: ${{ steps.version_vars.outputs.mastodon_short_sha }}
build-image:
needs: compute-suffix
@ -39,6 +41,7 @@ jobs:
latest=auto
tags: |
type=ref,event=pr
type=ref,event=pr,suffix=-${{ needs.compute-suffix.outputs.short_sha }}
secrets: inherit
build-image-streaming:
@ -55,4 +58,5 @@ jobs:
latest=auto
tags: |
type=ref,event=pr
type=ref,event=pr,suffix=-${{ needs.compute-suffix.outputs.short_sha }}
secrets: inherit

View File

@ -32,7 +32,7 @@ jobs:
labels: |
org.opencontainers.image.description=Nightly build image used for testing purposes
flavor: |
latest=true
latest=auto
tags: |
type=raw,value=edge
type=raw,value=nightly
@ -53,7 +53,7 @@ jobs:
labels: |
org.opencontainers.image.description=Nightly build image used for testing purposes
flavor: |
latest=true
latest=auto
tags: |
type=raw,value=edge
type=raw,value=nightly

View File

@ -2,7 +2,7 @@
All notable changes to this project will be documented in this file.
## [4.3.0] - UNRELEASED
## [4.3.0] - 2024-10-08
The following changelog entries focus on changes visible to users, administrators, client developers or federated software developers, but there has also been a lot of code modernization, refactoring, and tooling work, in particular by @mjankowski.
@ -11,12 +11,12 @@ The following changelog entries focus on changes visible to users, administrator
- **Add confirmation interstitial instead of silently redirecting logged-out visitors to remote resources** (#27792, #28902, and #30651 by @ClearlyClaire and @Gargron)\
This fixes a longstanding open redirect in Mastodon, at the cost of added friction when local links to remote resources are shared.
- Fix ReDoS vulnerability on some Ruby versions ([GHSA-jpxp-r43f-rhvx](https://github.com/mastodon/mastodon/security/advisories/GHSA-jpxp-r43f-rhvx))
- Change `form-action` Content-Security-Policy directive to be more restrictive (#26897 by @ClearlyClaire)
- Change `form-action` Content-Security-Policy directive to be more restrictive (#26897 and #32241 by @ClearlyClaire)
- Update dependencies
### Added
- **Add server-side notification grouping** (#29889, #30576, #30685, #30688, #30707, #30776, #30779, #30781, #30440, #31062, #31098, #31076, #31111, #31123, #31223, #31214, #31224, #31299, #31325, #31347, #31304, #31326, #31384, #31403, #31433, #31509, #31486, #31513, #31592, #31594, #31638, #31746, #31652, #31709, #31725, #31745, #31613, #31657, #31840, #31610, #31929, #32089 and #32085 by @ClearlyClaire, @Gargron, @mgmn, and @renchap)\
- **Add server-side notification grouping** (#29889, #30576, #30685, #30688, #30707, #30776, #30779, #30781, #30440, #31062, #31098, #31076, #31111, #31123, #31223, #31214, #31224, #31299, #31325, #31347, #31304, #31326, #31384, #31403, #31433, #31509, #31486, #31513, #31592, #31594, #31638, #31746, #31652, #31709, #31725, #31745, #31613, #31657, #31840, #31610, #31929, #32089, #32085, #32243, #32179 and #32254 by @ClearlyClaire, @Gargron, @mgmn, and @renchap)\
Group notifications of the same type for the same target, so that your notifications no longer get cluttered by boost and favorite notifications as soon as a couple of your posts get traction.\
This is done server-side so that clients can efficiently get relevant groups without having to go through numerous pages of individual notifications.\
As part of this, the visual design of the entire notifications feature has been revamped.\
@ -28,7 +28,7 @@ The following changelog entries focus on changes visible to users, administrator
- `GET /api/v2/notifications/:group_key/accounts`: https://docs.joinmastodon.org/methods/grouped_notifications/#get-group-accounts
- `POST /api/v2/notifications/:group_key/dimsiss`: https://docs.joinmastodon.org/methods/grouped_notifications/#dismiss-group
- `GET /api/v2/notifications/:unread_count`: https://docs.joinmastodon.org/methods/grouped_notifications/#unread-group-count
- **Add notification policies, filtered notifications and notification requests** (#29366, #29529, #29433, #29565, #29567, #29572, #29575, #29588, #29646, #29652, #29658, #29666, #29693, #29699, #29737, #29706, #29570, #29752, #29810, #29826, #30114, #30251, #30559, #29868, #31008, #31011, #30996, #31149, #31220, #31222, #31225, #31242, #31262, #31250, #31273, #31310, #31316, #31322, #31329, #31324, #31331, #31343, #31342, #31309, #31358, #31378, #31406, #31256, #31456, #31419, #31457, #31508, #31540, #31541, #31723 and #32062 by @ClearlyClaire, @Gargron, @TheEssem, @mgmn, @oneiros, and @renchap)\
- **Add notification policies, filtered notifications and notification requests** (#29366, #29529, #29433, #29565, #29567, #29572, #29575, #29588, #29646, #29652, #29658, #29666, #29693, #29699, #29737, #29706, #29570, #29752, #29810, #29826, #30114, #30251, #30559, #29868, #31008, #31011, #30996, #31149, #31220, #31222, #31225, #31242, #31262, #31250, #31273, #31310, #31316, #31322, #31329, #31324, #31331, #31343, #31342, #31309, #31358, #31378, #31406, #31256, #31456, #31419, #31457, #31508, #31540, #31541, #31723, #32062 and #32281 by @ClearlyClaire, @Gargron, @TheEssem, @mgmn, @oneiros, and @renchap)\
The old “Block notifications from non-followers”, “Block notifications from people you don't follow” and “Block direct messages from people you don't follow” notification settings have been replaced by a new set of settings found directly in the notification column.\
You can now separately filter or drop notifications from people you don't follow, people who don't follow you, accounts created within the past 30 days, as well as unsolicited private mentions, and accounts limited by the moderation.\
Instead of being outright dropped, notifications that you chose to filter are put in a separate “Filtered notifications” box that you can review separately without it clogging your main notifications.\
@ -61,7 +61,7 @@ The following changelog entries focus on changes visible to users, administrator
- **Add timeline of public posts about a trending link** (#30381 and #30840 by @Gargron)\
You can now see public posts mentioning currently-trending articles from people who have opted into discovery features.\
This adds a new REST API endpoint: https://docs.joinmastodon.org/methods/timelines/#link
- **Add author highlight for news articles whose authors are on the fediverse** (#30398, #30670, #30521, #30846, #31819, and #31900 by @Gargron and @oneiros)\
- **Add author highlight for news articles whose authors are on the fediverse** (#30398, #30670, #30521, #30846, #31819, #31900 and #32188 by @Gargron, @mjankowski and @oneiros)\
This adds a mechanism to [highlight the author of news articles](https://blog.joinmastodon.org/2024/07/highlighting-journalism-on-mastodon/) shared on Mastodon.\
Articles hosted outside the fediverse can indicate a fediverse author with a meta tag:
```html
@ -150,10 +150,12 @@ The following changelog entries focus on changes visible to users, administrator
- Add groundwork for annual reports for accounts (#28693 by @Gargron)\
This lays the groundwork for a “year-in-review”/“wrapped” style report for local users, but is currently not in use.
- Add notification email on invalid second authenticator (#28822 by @ClearlyClaire)
- Add date of account deletion in list of accounts in the admin interface (#25640 by @tribela)
- Add new emojis from `jdecked/twemoji` 15.0 (#28404 by @TheEssem)
- Add configurable error handling in attachment batch deletion (#28184 by @vmstan)\
This makes the S3 batch size configurable through the `S3_BATCH_DELETE_LIMIT` environment variable (defaults to 1000), and adds some retry logic, configurable through the `S3_BATCH_DELETE_RETRY` environment variable (defaults to 3).
- Add VAPID public key to instance serializer (#28006 by @ThisIsMissEm)
- Add support for serving JRD `/.well-known/host-meta.json` in addition to XRD host-meta (#32206 by @c960657)
- Add `nodeName` and `nodeDescription` to nodeinfo `metadata` (#28079 by @6543)
- Add Thai diacritics and tone marks in `HASHTAG_INVALID_CHARS_RE` (#26576 by @ppnplus)
- Add variable delay before link verification of remote account links (#27774 by @ClearlyClaire)
@ -168,7 +170,7 @@ The following changelog entries focus on changes visible to users, administrator
### Changed
- **Change icons throughout the web interface** (#27385, #27539, #27555, #27579, #27700, #27817, #28519, #28709, #28064, #28775, #28780, #27924, #29294, #29395, #29537, #29569, #29610, #29612, #29649, #29844, #27780, #30974, #30963, #30962, #30961, #31362, #31363, #31359, #31371, #31360, #31512, #31511, and #31525 by @ClearlyClaire, @Gargron, @arbolitoloco1, @mjankowski, @nclm, @renchap, @ronilaukkarinen, and @zunda)\
- **Change icons throughout the web interface** (#27385, #27539, #27555, #27579, #27700, #27817, #28519, #28709, #28064, #28775, #28780, #27924, #29294, #29395, #29537, #29569, #29610, #29612, #29649, #29844, #27780, #30974, #30963, #30962, #30961, #31362, #31363, #31359, #31371, #31360, #31512, #31511, #31525, #32153, and #32201 by @ClearlyClaire, @Gargron, @arbolitoloco1, @mjankowski, @nclm, @renchap, @ronilaukkarinen, and @zunda)\
This changes all the interface icons from FontAwesome to Material Symbols for a more modern look, consistent with the official Mastodon Android app.\
In addition, better care is given to pixel alignment, and icon variants are used to better highlight active/inactive state.
- **Change design of compose form in web UI** (#28119, #29059, #29248, #29372, #29384, #29417, #29456, #29406, #29651, #29659, #31889 and #32033 by @ClearlyClaire, @Gargron, @eai04191, @hinaloe, and @ronilaukkarinen)\
@ -192,9 +194,9 @@ The following changelog entries focus on changes visible to users, administrator
Administrators may need to update their setup accordingly.
- Change how content warnings and filters are displayed in web UI (#31365, and #31761 by @Gargron)
- Change preview card processing to ignore `undefined` as canonical url (#31882 by @oneiros)
- Change embedded posts to use web UI (#31766 and #32135 by @Gargron)
- Change embedded posts to use web UI (#31766, #32135 and #32271 by @Gargron)
- Change inner borders in media galleries in web UI (#31852 by @Gargron)
- Change design of media attachments and profile media tab in web UI (#31807, #32048, and #31967 by @Gargron)
- Change design of media attachments and profile media tab in web UI (#31807, #32048, #31967, #32217, #32224 and #32257 by @ClearlyClaire and @Gargron)
- Change labels on thread indicators in web UI (#31806 by @Gargron)
- Change label of "Data export" menu item in settings interface (#32099 by @c960657)
- Change responsive break points on navigation panel in web UI (#32034 by @Gargron)
@ -284,9 +286,10 @@ The following changelog entries focus on changes visible to users, administrator
- Fix error when accepting an appeal for sensitive posts deleted in the meantime (#32037 by @ClearlyClaire)
- Fix error when encountering reblog of deleted post in feed rebuild (#32001 by @ClearlyClaire)
- Fix Safari browser glitch related to horizontal scrolling (#31960 by @Gargron)
- Fix unresolvable mentions sometimes preventing processing incoming posts (#29215 by @tribela and @ClearlyClaire)
- Fix too many requests caused by relationship look-ups in web UI (#32042 by @Gargron)
- Fix links for reblogs in moderation interface (#31979 by @ClearlyClaire)
- Fix the appearance of avatars when they do not load (#31966 by @renchap)
- Fix the appearance of avatars when they do not load (#31966 and #32270 by @Gargron and @renchap)
- Fix spurious error notifications for aborted requests in web UI (#31952 by @c960657)
- Fix HTTP 500 error in `/api/v1/polls/:id/votes` when required `choices` parameter is missing (#25598 by @danielmbrasil)
- Fix security context sometimes not being added in LD-Signed activities (#31871 by @ClearlyClaire)
@ -309,10 +312,12 @@ The following changelog entries focus on changes visible to users, administrator
- Fix “Redirect URI” field not being marked as required in “New application” form (#30311 by @ThisIsMissEm)
- Fix right-to-left text in preview cards (#30930 by @ClearlyClaire)
- Fix rack attack `match_type` value typo in logging config (#30514 by @mjankowski)
- Fix various cases of duplicate, missing, or inconsistent borders or scrollbar styles (#31068, #31286, #31268, #31275, #31284, #31305, #31346, #31372, #31373, #31389, #31432, #31391, #31445 and #32091 by @ClearlyClaire, @valtlai and @vmstan)
- Fix various cases of duplicate, missing, or inconsistent borders or scrollbar styles (#31068, #31286, #31268, #31275, #31284, #31305, #31346, #31372, #31373, #31389, #31432, #31391, #31445, #32091, #32147 and #32137 by @ClearlyClaire, @mjankowski, @valtlai and @vmstan)
- Fix editing description of media uploads with custom thumbnails (#32221 by @ClearlyClaire)
- Fix race condition in `POST /api/v1/push/subscription` (#30166 by @ClearlyClaire)
- Fix post deletion not being delayed when those are part of an account warning (#30163 by @ClearlyClaire)
- Fix rendering error on `/start` when not logged in (#30023 by @timothyjrogers)
- Fix unneeded requests to blocked domains when receiving relayed signed activities from them (#31161 by @ClearlyClaire)
- Fix logo pushing header buttons out of view on certain conditions in mobile layout (#29787 by @ClearlyClaire)
- Fix notification-related records not being reattributed when merging accounts (#29694 by @ClearlyClaire)
- Fix results/query in `api/v1/featured_tags/suggestions` (#29597 by @mjankowski)
@ -322,6 +327,7 @@ The following changelog entries focus on changes visible to users, administrator
- Fix full date display not respecting the locale 12/24h format (#29448 by @renchap)
- Fix filters title and keywords overflow (#29396 by @GeopJr)
- Fix incorrect date format in “Follows and followers” (#29390 by @JasonPunyon)
- Fix navigation item active highlight for some paths (#32159 by @mjankowski)
- Fix “Edit media” modal sizing and layout when space-constrained (#27095 by @ronilaukkarinen)
- Fix modal container bounds (#29185 by @nico3333fr)
- Fix inefficient HTTP signature parsing using regexps and `StringScanner` (#29133 by @ClearlyClaire)

View File

@ -615,7 +615,7 @@ GEM
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.8.1)
rack (2.2.9)
rack (2.2.10)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
rack-cors (2.0.2)

View File

@ -13,8 +13,9 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through
## Supported Versions
| Version | Supported |
| ------- | --------- |
| 4.2.x | Yes |
| 4.1.x | Yes |
| < 4.1 | No |
| Version | Supported |
| ------- | ---------------- |
| 4.3.x | Yes |
| 4.2.x | Yes |
| 4.1.x | Until 2025-04-08 |
| < 4.1 | No |

View File

@ -52,7 +52,7 @@ class Api::V1::Notifications::RequestsController < Api::BaseController
private
def load_requests
requests = NotificationRequest.where(account: current_account).includes(:last_status, from_account: [:account_stat, :user]).to_a_paginated_by_id(
requests = NotificationRequest.where(account: current_account).without_suspended.includes(:last_status, from_account: [:account_stat, :user]).to_a_paginated_by_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),
params_slice(:max_id, :since_id, :min_id)
)

View File

@ -35,7 +35,7 @@ class ApplicationController < ActionController::Base
rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
rescue_from Mastodon::RateLimitExceededError, with: :too_many_requests
rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error
rescue_from(*Mastodon::HTTP_CONNECTION_ERRORS, with: :internal_server_error)
rescue_from Mastodon::RaceConditionError, Stoplight::Error::RedLight, ActiveRecord::SerializationFailure, with: :service_unavailable
rescue_from Seahorse::Client::NetworkingError do |e|

View File

@ -20,7 +20,7 @@ module Api::ErrorHandling
render json: { error: 'Record not found' }, status: 404
end
rescue_from HTTP::Error, Mastodon::UnexpectedResponseError do
rescue_from(*Mastodon::HTTP_CONNECTION_ERRORS, Mastodon::UnexpectedResponseError) do
render json: { error: 'Remote data could not be fetched' }, status: 503
end

View File

@ -80,7 +80,7 @@ module SignatureVerification
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)", signed_string: compare_signed_string, signature: signature_params['signature']
rescue SignatureVerificationError => e
fail_with! e.message
rescue HTTP::Error, OpenSSL::SSL::SSLError => e
rescue *Mastodon::HTTP_CONNECTION_ERRORS => e
fail_with! "Failed to fetch remote data: #{e.message}"
rescue Mastodon::UnexpectedResponseError
fail_with! 'Failed to fetch remote data (got unexpected reply from server)'

View File

@ -13,7 +13,7 @@ class MediaProxyController < ApplicationController
rescue_from ActiveRecord::RecordInvalid, with: :not_found
rescue_from Mastodon::UnexpectedResponseError, with: :not_found
rescue_from Mastodon::NotPermittedError, with: :not_found
rescue_from HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, with: :internal_server_error
rescue_from(*Mastodon::HTTP_CONNECTION_ERRORS, with: :internal_server_error)
def show
with_redis_lock("media_download:#{params[:id]}") do

View File

@ -193,6 +193,7 @@ module LanguagesHelper
ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze,
cnr: ['Montenegrin', 'crnogorski'].freeze,
csb: ['Kashubian', 'Kaszëbsczi'].freeze,
gsw: ['Swiss German', 'Schwiizertütsch'].freeze,
jbo: ['Lojban', 'la .lojban.'].freeze,
kab: ['Kabyle', 'Taqbaylit'].freeze,
ldn: ['Láadan', 'Láadan'].freeze,

View File

@ -13,7 +13,7 @@ export interface ApiAccountRoleJSON {
}
// See app/serializers/rest/account_serializer.rb
export interface ApiAccountJSON {
export interface BaseApiAccountJSON {
acct: string;
avatar: string;
avatar_static: string;
@ -45,3 +45,12 @@ export interface ApiAccountJSON {
memorial?: boolean;
hide_collections: boolean;
}
// See app/serializers/rest/muted_account_serializer.rb
export interface ApiMutedAccountJSON extends BaseApiAccountJSON {
mute_expires_at?: string | null;
}
// For now, we have the same type representing both `Account` and `MutedAccount`
// objects, but we should refactor this in the future.
export type ApiAccountJSON = ApiMutedAccountJSON;

View File

@ -129,8 +129,13 @@ export const InlineFollowSuggestions = ({ hidden }) => {
return;
}
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
if (getComputedStyle(bodyRef.current).direction === 'rtl') {
setCanScrollLeft((bodyRef.current.clientWidth - bodyRef.current.scrollLeft) < bodyRef.current.scrollWidth);
setCanScrollRight(bodyRef.current.scrollLeft < 0);
} else {
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
}
}, [setCanScrollRight, setCanScrollLeft, bodyRef, suggestions]);
const handleLeftNav = useCallback(() => {

View File

@ -16,6 +16,7 @@ import {
import type { IconProp } from 'flavours/glitch/components/icon';
import { Icon } from 'flavours/glitch/components/icon';
import Status from 'flavours/glitch/containers/status_container';
import { getStatusHidden } from 'flavours/glitch/selectors/filters';
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
import { DisplayedName } from './displayed_name';
@ -51,6 +52,12 @@ export const NotificationWithStatus: React.FC<{
(state) => state.statuses.getIn([statusId, 'visibility']) === 'direct',
);
const isFiltered = useAppSelector(
(state) =>
statusId &&
getStatusHidden(state, { id: statusId, contextType: 'notifications' }),
);
const handlers = useMemo(
() => ({
open: () => {
@ -77,7 +84,7 @@ export const NotificationWithStatus: React.FC<{
[dispatch, statusId],
);
if (!statusId) return null;
if (!statusId || isFiltered) return null;
return (
<HotKeys handlers={handlers}>

View File

@ -15,6 +15,7 @@ import { Icon } from 'flavours/glitch/components/icon';
import {
selectSettingsNotificationsQuickFilterActive,
selectSettingsNotificationsQuickFilterAdvanced,
selectSettingsNotificationsQuickFilterShow,
} from 'flavours/glitch/selectors/settings';
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
@ -70,6 +71,11 @@ export const FilterBar: React.FC = () => {
const advancedMode = useAppSelector(
selectSettingsNotificationsQuickFilterAdvanced,
);
const useFilterBar = useAppSelector(
selectSettingsNotificationsQuickFilterShow,
);
if (!useFilterBar) return null;
if (advancedMode)
return (

View File

@ -21,6 +21,7 @@ import { Permalink } from 'flavours/glitch/components/permalink';
import { PictureInPicture } from 'flavours/glitch/features/picture_in_picture';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { layoutFromWindow } from 'flavours/glitch/is_mobile';
import { selectUnreadNotificationGroupsCount } from 'flavours/glitch/selectors/notifications';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose';
@ -90,7 +91,7 @@ const mapStateToProps = state => ({
hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
canUploadMore: !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) && state.getIn(['compose', 'media_attachments']).size < 4,
isWide: state.getIn(['local_settings', 'stretch']),
unreadNotifications: state.getIn(['notifications', 'unread']),
unreadNotifications: selectUnreadNotificationGroupsCount(state),
showFaviconBadge: state.getIn(['local_settings', 'notifications', 'favicon_badge']),
hicolorPrivacyIcons: state.getIn(['local_settings', 'hicolor_privacy_icons']),
moved: state.getIn(['accounts', me, 'moved']) && state.getIn(['accounts', state.getIn(['accounts', me, 'moved'])]),

View File

@ -95,6 +95,9 @@ export const accountDefaultValues: AccountShape = {
limited: false,
moved: null,
hide_collections: false,
// This comes from `ApiMutedAccountJSON`, but we should eventually
// store that in a different object.
mute_expires_at: null,
};
const AccountFactory = ImmutableRecord<AccountShape>(accountDefaultValues);

View File

@ -559,7 +559,10 @@ export const notificationGroupsReducer = createReducer<NotificationGroupsState>(
compareId(state.lastReadId, mostRecentGroup.page_max_id) < 0
)
state.lastReadId = mostRecentGroup.page_max_id;
commitLastReadId(state);
// We don't call `commitLastReadId`, because that is conditional
// and we want to unconditionally update the state instead.
state.readMarkerId = state.lastReadId;
})
.addCase(fetchMarkers.fulfilled, (state, action) => {
if (

View File

@ -0,0 +1,50 @@
import { createSelector } from '@reduxjs/toolkit';
import type { RootState } from 'flavours/glitch/store';
import { toServerSideType } from 'flavours/glitch/utils/filters';
// TODO: move to `app/javascript/flavours/glitch/models` and use more globally
type Filter = Immutable.Map<string, unknown>;
// TODO: move to `app/javascript/flavours/glitch/models` and use more globally
type FilterResult = Immutable.Map<string, unknown>;
export const getFilters = createSelector(
[
(state: RootState) => state.filters as Immutable.Map<string, Filter>,
(_, { contextType }: { contextType: string }) => contextType,
],
(filters, contextType) => {
if (!contextType) {
return null;
}
const now = new Date();
const serverSideType = toServerSideType(contextType);
return filters.filter((filter) => {
const context = filter.get('context') as Immutable.List<string>;
const expiration = filter.get('expires_at') as Date | null;
return (
context.includes(serverSideType) &&
(expiration === null || expiration > now)
);
});
},
);
export const getStatusHidden = (
state: RootState,
{ id, contextType }: { id: string; contextType: string },
) => {
const filters = getFilters(state, { contextType });
if (filters === null) return false;
const filtered = state.statuses.getIn([id, 'filtered']) as
| Immutable.List<FilterResult>
| undefined;
return filtered?.some(
(result) =>
filters.getIn([result.get('filter'), 'filter_action']) === 'hide',
);
};

View File

@ -1,23 +1,12 @@
import { createSelector } from '@reduxjs/toolkit';
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import { toServerSideType } from 'flavours/glitch/utils/filters';
import { me } from '../initial_state';
import { getFilters } from './filters';
export { makeGetAccount } from "./accounts";
const getFilters = createSelector([state => state.get('filters'), (_, { contextType }) => contextType], (filters, contextType) => {
if (!contextType) {
return null;
}
const now = new Date();
const serverSideType = toServerSideType(contextType);
return filters.filter(filter => filter.get('context').includes(serverSideType) && (filter.get('expires_at') === null || filter.get('expires_at') > now));
});
export const makeGetStatus = () => {
return createSelector(
[

View File

@ -1050,6 +1050,12 @@ a.name-tag,
color: var(--user-role-accent);
}
.applications-list {
.icon {
vertical-align: middle;
}
}
.announcements-list,
.filters-list {
border: 1px solid var(--background-border-color);

View File

@ -8542,79 +8542,23 @@ noscript {
background: rgba($base-overlay-background, 0.5);
}
.list-adder,
.list-editor {
background: $ui-base-color;
backdrop-filter: var(--background-filter);
background: var(--modal-background-color);
border: 1px solid var(--modal-border-color);
flex-direction: column;
border-radius: 8px;
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
width: 380px;
overflow: hidden;
@media screen and (width <= 420px) {
width: 90%;
}
h4 {
padding: 15px 0;
background: lighten($ui-base-color, 13%);
font-weight: 500;
font-size: 16px;
text-align: center;
border-radius: 8px 8px 0 0;
}
.drawer__pager {
height: 50vh;
border-radius: 4px;
}
.drawer__inner {
border-radius: 0 0 8px 8px;
&.backdrop {
width: calc(100% - 60px);
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
border-radius: 0 0 0 8px;
}
}
&__accounts {
overflow-y: auto;
}
.account__display-name {
&:hover strong {
text-decoration: none;
}
}
.account__avatar {
cursor: default;
}
.search {
margin-bottom: 0;
}
}
.list-adder {
background: $ui-base-color;
flex-direction: column;
border-radius: 8px;
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
width: 380px;
overflow: hidden;
@media screen and (width <= 420px) {
width: 90%;
}
&__account {
background: lighten($ui-base-color, 13%);
}
&__lists {
background: lighten($ui-base-color, 13%);
height: 50vh;
border-radius: 0 0 8px 8px;
overflow-y: auto;
@ -8635,6 +8579,52 @@ noscript {
text-decoration: none;
font-size: 16px;
padding: 10px;
display: flex;
align-items: center;
gap: 4px;
}
}
.list-editor {
h4 {
padding: 15px 0;
background: lighten($ui-base-color, 13%);
font-weight: 500;
font-size: 16px;
text-align: center;
border-radius: 8px 8px 0 0;
}
.drawer__pager {
height: 50vh;
border: 0;
}
.drawer__inner {
&.backdrop {
width: calc(100% - 60px);
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
border-radius: 0 0 0 8px;
}
}
&__accounts {
background: unset;
overflow-y: auto;
}
.account__display-name {
&:hover strong {
text-decoration: none;
}
}
.account__avatar {
cursor: default;
}
.search {
margin-bottom: 0;
}
}

View File

@ -13,7 +13,7 @@ export interface ApiAccountRoleJSON {
}
// See app/serializers/rest/account_serializer.rb
export interface ApiAccountJSON {
export interface BaseApiAccountJSON {
acct: string;
avatar: string;
avatar_static: string;
@ -45,3 +45,12 @@ export interface ApiAccountJSON {
memorial?: boolean;
hide_collections: boolean;
}
// See app/serializers/rest/muted_account_serializer.rb
export interface ApiMutedAccountJSON extends BaseApiAccountJSON {
mute_expires_at?: string | null;
}
// For now, we have the same type representing both `Account` and `MutedAccount`
// objects, but we should refactor this in the future.
export type ApiAccountJSON = ApiMutedAccountJSON;

View File

@ -129,8 +129,13 @@ export const InlineFollowSuggestions = ({ hidden }) => {
return;
}
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
if (getComputedStyle(bodyRef.current).direction === 'rtl') {
setCanScrollLeft((bodyRef.current.clientWidth - bodyRef.current.scrollLeft) < bodyRef.current.scrollWidth);
setCanScrollRight(bodyRef.current.scrollLeft < 0);
} else {
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
}
}, [setCanScrollRight, setCanScrollLeft, bodyRef, suggestions]);
const handleLeftNav = useCallback(() => {

View File

@ -13,6 +13,7 @@ import {
import type { IconProp } from 'mastodon/components/icon';
import { Icon } from 'mastodon/components/icon';
import Status from 'mastodon/containers/status_container';
import { getStatusHidden } from 'mastodon/selectors/filters';
import { useAppSelector, useAppDispatch } from 'mastodon/store';
import { DisplayedName } from './displayed_name';
@ -48,6 +49,12 @@ export const NotificationWithStatus: React.FC<{
(state) => state.statuses.getIn([statusId, 'visibility']) === 'direct',
);
const isFiltered = useAppSelector(
(state) =>
statusId &&
getStatusHidden(state, { id: statusId, contextType: 'notifications' }),
);
const handlers = useMemo(
() => ({
open: () => {
@ -73,7 +80,7 @@ export const NotificationWithStatus: React.FC<{
[dispatch, statusId],
);
if (!statusId) return null;
if (!statusId || isFiltered) return null;
return (
<HotKeys handlers={handlers}>

View File

@ -85,6 +85,7 @@
"alert.rate_limited.title": "Ліміт перавышаны",
"alert.unexpected.message": "Узнікла нечаканая памылка.",
"alert.unexpected.title": "Вой!",
"alt_text_badge.title": "Альтернативный текст",
"announcement.announcement": "Аб'ява",
"attachments_list.unprocessed": "(неапрацаваны)",
"audio.hide": "Схаваць аўдыя",

View File

@ -62,7 +62,7 @@
"account.requested_follow": "{name} möchte dir folgen",
"account.share": "Profil von @{name} teilen",
"account.show_reblogs": "Geteilte Beiträge von @{name} anzeigen",
"account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} posts}}",
"account.statuses_counter": "{count, plural, one {{counter} Beitrag} other {{counter} Beiträge}}",
"account.unblock": "Blockierung von @{name} aufheben",
"account.unblock_domain": "Blockierung von {domain} aufheben",
"account.unblock_short": "Blockierung aufheben",

View File

@ -95,6 +95,9 @@ export const accountDefaultValues: AccountShape = {
limited: false,
moved: null,
hide_collections: false,
// This comes from `ApiMutedAccountJSON`, but we should eventually
// store that in a different object.
mute_expires_at: null,
};
const AccountFactory = ImmutableRecord<AccountShape>(accountDefaultValues);

View File

@ -559,7 +559,10 @@ export const notificationGroupsReducer = createReducer<NotificationGroupsState>(
compareId(state.lastReadId, mostRecentGroup.page_max_id) < 0
)
state.lastReadId = mostRecentGroup.page_max_id;
commitLastReadId(state);
// We don't call `commitLastReadId`, because that is conditional
// and we want to unconditionally update the state instead.
state.readMarkerId = state.lastReadId;
})
.addCase(fetchMarkers.fulfilled, (state, action) => {
if (

View File

@ -0,0 +1,50 @@
import { createSelector } from '@reduxjs/toolkit';
import type { RootState } from 'mastodon/store';
import { toServerSideType } from 'mastodon/utils/filters';
// TODO: move to `app/javascript/mastodon/models` and use more globally
type Filter = Immutable.Map<string, unknown>;
// TODO: move to `app/javascript/mastodon/models` and use more globally
type FilterResult = Immutable.Map<string, unknown>;
export const getFilters = createSelector(
[
(state: RootState) => state.filters as Immutable.Map<string, Filter>,
(_, { contextType }: { contextType: string }) => contextType,
],
(filters, contextType) => {
if (!contextType) {
return null;
}
const now = new Date();
const serverSideType = toServerSideType(contextType);
return filters.filter((filter) => {
const context = filter.get('context') as Immutable.List<string>;
const expiration = filter.get('expires_at') as Date | null;
return (
context.includes(serverSideType) &&
(expiration === null || expiration > now)
);
});
},
);
export const getStatusHidden = (
state: RootState,
{ id, contextType }: { id: string; contextType: string },
) => {
const filters = getFilters(state, { contextType });
if (filters === null) return false;
const filtered = state.statuses.getIn([id, 'filtered']) as
| Immutable.List<FilterResult>
| undefined;
return filtered?.some(
(result) =>
filters.getIn([result.get('filter'), 'filter_action']) === 'hide',
);
};

View File

@ -1,23 +1,12 @@
import { createSelector } from '@reduxjs/toolkit';
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import { toServerSideType } from 'mastodon/utils/filters';
import { me } from '../initial_state';
import { getFilters } from './filters';
export { makeGetAccount } from "./accounts";
const getFilters = createSelector([state => state.get('filters'), (_, { contextType }) => contextType], (filters, contextType) => {
if (!contextType) {
return null;
}
const now = new Date();
const serverSideType = toServerSideType(contextType);
return filters.filter(filter => filter.get('context').includes(serverSideType) && (filter.get('expires_at') === null || filter.get('expires_at') > now));
});
export const makeGetStatus = () => {
return createSelector(
[

View File

@ -1045,6 +1045,12 @@ a.name-tag,
color: var(--user-role-accent);
}
.applications-list {
.icon {
vertical-align: middle;
}
}
.announcements-list,
.filters-list {
border: 1px solid var(--background-border-color);

View File

@ -2878,7 +2878,7 @@ $ui-header-logo-wordmark-width: 99px;
}
.column {
width: 400px;
width: clamp(380px, calc((100% - 350px) / 4), 400px);
position: relative;
box-sizing: border-box;
display: flex;
@ -8007,79 +8007,23 @@ noscript {
background: rgba($base-overlay-background, 0.5);
}
.list-adder,
.list-editor {
background: $ui-base-color;
backdrop-filter: var(--background-filter);
background: var(--modal-background-color);
border: 1px solid var(--modal-border-color);
flex-direction: column;
border-radius: 8px;
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
width: 380px;
overflow: hidden;
@media screen and (width <= 420px) {
width: 90%;
}
h4 {
padding: 15px 0;
background: lighten($ui-base-color, 13%);
font-weight: 500;
font-size: 16px;
text-align: center;
border-radius: 8px 8px 0 0;
}
.drawer__pager {
height: 50vh;
border-radius: 4px;
}
.drawer__inner {
border-radius: 0 0 8px 8px;
&.backdrop {
width: calc(100% - 60px);
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
border-radius: 0 0 0 8px;
}
}
&__accounts {
overflow-y: auto;
}
.account__display-name {
&:hover strong {
text-decoration: none;
}
}
.account__avatar {
cursor: default;
}
.search {
margin-bottom: 0;
}
}
.list-adder {
background: $ui-base-color;
flex-direction: column;
border-radius: 8px;
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
width: 380px;
overflow: hidden;
@media screen and (width <= 420px) {
width: 90%;
}
&__account {
background: lighten($ui-base-color, 13%);
}
&__lists {
background: lighten($ui-base-color, 13%);
height: 50vh;
border-radius: 0 0 8px 8px;
overflow-y: auto;
@ -8100,6 +8044,52 @@ noscript {
text-decoration: none;
font-size: 16px;
padding: 10px;
display: flex;
align-items: center;
gap: 4px;
}
}
.list-editor {
h4 {
padding: 15px 0;
background: lighten($ui-base-color, 13%);
font-weight: 500;
font-size: 16px;
text-align: center;
border-radius: 8px 8px 0 0;
}
.drawer__pager {
height: 50vh;
border: 0;
}
.drawer__inner {
&.backdrop {
width: calc(100% - 60px);
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
border-radius: 0 0 0 8px;
}
}
&__accounts {
background: unset;
overflow-y: auto;
}
.account__display-name {
&:hover strong {
text-decoration: none;
}
}
.account__avatar {
cursor: default;
}
.search {
margin-bottom: 0;
}
}

View File

@ -199,7 +199,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
return if account.nil?
@mentions << Mention.new(account: account, silent: false)
rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError
rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
@unresolved_mentions << tag['href']
end
@ -250,7 +250,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
media_attachment.download_file!
media_attachment.download_thumbnail!
media_attachment.save
rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError
rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
RedownloadMediaWorker.perform_in(rand(30..600).seconds, media_attachment.id)
rescue Seahorse::Client::NetworkingError => e
Rails.logger.warn "Error storing media attachment: #{e}"

View File

@ -35,7 +35,7 @@ class AccountAlias < ApplicationRecord
def set_uri
target_account = ResolveAccountService.new.call(acct)
self.uri = ActivityPub::TagManager.instance.uri_for(target_account) unless target_account.nil?
rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
rescue Webfinger::Error, *Mastodon::HTTP_CONNECTION_ERRORS, Mastodon::Error
# Validation will take care of it
end

View File

@ -61,7 +61,7 @@ class AccountMigration < ApplicationRecord
def set_target_account
self.target_account = ResolveAccountService.new.call(acct, skip_cache: true)
rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error, Addressable::URI::InvalidURIError
rescue Webfinger::Error, *Mastodon::HTTP_CONNECTION_ERRORS, Mastodon::Error, Addressable::URI::InvalidURIError
# Validation will take care of it
end

View File

@ -26,7 +26,7 @@ module Remotable
public_send(:"#{attachment_name}=", ResponseWithLimit.new(response, limit))
end
rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError => e
rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS => e
Rails.logger.debug { "Error fetching remote #{attachment_name}: #{e}" }
public_send(:"#{attachment_name}=", nil) if public_send(:"#{attachment_name}_file_name").present?
raise e unless suppress_errors

View File

@ -40,7 +40,9 @@ class DomainBlock < ApplicationRecord
if suspend?
[:suspend]
else
[severity.to_sym, reject_media? ? :reject_media : nil, reject_reports? ? :reject_reports : nil].reject { |policy| policy == :noop || policy.nil? }
[severity.to_sym, reject_media? ? :reject_media : nil, reject_reports? ? :reject_reports : nil]
.reject { |policy| policy == :noop }
.compact
end
end

View File

@ -18,5 +18,6 @@ class FollowRecommendation < ApplicationRecord
belongs_to :account_summary, foreign_key: :account_id, inverse_of: false
belongs_to :account
scope :localized, ->(locale) { joins(:account_summary).merge(AccountSummary.localized(locale)) }
scope :unsupressed, -> { where.not(FollowRecommendationSuppression.where(FollowRecommendationSuppression.arel_table[:account_id].eq(arel_table[:account_id])).select(1).arel.exists) }
scope :localized, ->(locale) { unsupressed.joins(:account_summary).merge(AccountSummary.localized(locale)) }
end

View File

@ -32,7 +32,7 @@ class Form::Redirect
def set_target_account
@target_account = ResolveAccountService.new.call(acct, skip_cache: true)
rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error, Addressable::URI::InvalidURIError
rescue Webfinger::Error, *Mastodon::HTTP_CONNECTION_ERRORS, Mastodon::Error, Addressable::URI::InvalidURIError
# Validation will take care of it
end

View File

@ -62,6 +62,6 @@ class NotificationPolicy < ApplicationRecord
private
def pending_notification_requests
@pending_notification_requests ||= notification_requests.limit(MAX_MEANINGFUL_COUNT).pick(Arel.sql('count(*), coalesce(sum(notifications_count), 0)::bigint'))
@pending_notification_requests ||= notification_requests.without_suspended.limit(MAX_MEANINGFUL_COUNT).pick(Arel.sql('count(*), coalesce(sum(notifications_count), 0)::bigint'))
end
end

View File

@ -26,6 +26,8 @@ class NotificationRequest < ApplicationRecord
before_save :prepare_notifications_count
scope :without_suspended, -> { joins(:from_account).merge(Account.without_suspended) }
def self.preload_cache_collection(requests)
cached_statuses_by_id = yield(requests.filter_map(&:last_status)).index_by(&:id) # Call cache_collection in block

View File

@ -66,7 +66,7 @@ class RemoteFollow
def acct_resource
@acct_resource ||= Webfinger.new("acct:#{acct}").perform
rescue Webfinger::Error, HTTP::ConnectionError
rescue Webfinger::Error, *Mastodon::HTTP_CONNECTION_ERRORS
nil
end

View File

@ -8,7 +8,8 @@ class ManifestSerializer < ActiveModel::Serializer
attributes :id, :name, :short_name,
:icons, :theme_color, :background_color,
:display, :start_url, :scope,
:share_target, :shortcuts
:share_target, :shortcuts,
:prefer_related_applications, :related_applications
def id
# This is set to `/home` because that was the old value of `start_url` and
@ -89,4 +90,28 @@ class ManifestSerializer < ActiveModel::Serializer
},
]
end
def prefer_related_applications
true
end
def related_applications
[
{
platform: 'play',
url: 'https://play.google.com/store/apps/details?id=org.joinmastodon.android',
id: 'org.joinmastodon.android',
},
{
platform: 'itunes',
url: 'https://apps.apple.com/us/app/mastodon-for-iphone/id1571998974',
id: 'id1571998974',
},
{
platform: 'f-droid',
url: 'https://f-droid.org/en/packages/org.joinmastodon.android/',
id: 'org.joinmastodon.android',
},
]
end
end

View File

@ -127,13 +127,13 @@ class ActivityPub::ProcessAccountService < BaseService
begin
@account.avatar_remote_url = image_url('icon') || '' unless skip_download?
@account.avatar = nil if @account.avatar_remote_url.blank?
rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError
rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
RedownloadAvatarWorker.perform_in(rand(30..600).seconds, @account.id)
end
begin
@account.header_remote_url = image_url('image') || '' unless skip_download?
@account.header = nil if @account.header_remote_url.blank?
rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError
rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
RedownloadHeaderWorker.perform_in(rand(30..600).seconds, @account.id)
end
@account.statuses_count = outbox_total_items if outbox_total_items.present?
@ -276,7 +276,7 @@ class ActivityPub::ProcessAccountService < BaseService
total_items = collection.is_a?(Hash) && collection['totalItems'].present? && collection['totalItems'].is_a?(Numeric) ? collection['totalItems'] : nil
has_first_page = collection.is_a?(Hash) && collection['first'].present?
@collections[type] = [total_items, has_first_page]
rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::LengthValidationError
rescue *Mastodon::HTTP_CONNECTION_ERRORS, Mastodon::LengthValidationError
@collections[type] = [nil, nil]
end

View File

@ -109,7 +109,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
media_attachment.download_file! if media_attachment.remote_url_previously_changed?
media_attachment.download_thumbnail! if media_attachment.thumbnail_remote_url_previously_changed?
media_attachment.save
rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError
rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
RedownloadMediaWorker.perform_in(rand(30..600).seconds, media_attachment.id)
rescue Seahorse::Client::NetworkingError => e
Rails.logger.warn "Error storing media attachment: #{e}"

View File

@ -29,7 +29,7 @@ class FetchLinkCardService < BaseService
end
attach_card if @card&.persisted?
rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, Encoding::UndefinedConversionError, ActiveRecord::RecordInvalid => e
rescue *Mastodon::HTTP_CONNECTION_ERRORS, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, Encoding::UndefinedConversionError, ActiveRecord::RecordInvalid => e
Rails.logger.debug { "Error fetching link #{@original_url}: #{e}" }
nil
end

View File

@ -12,7 +12,7 @@ class FetchResourceService < BaseService
return if url.blank?
process(url)
rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
rescue *Mastodon::HTTP_CONNECTION_ERRORS, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
Rails.logger.debug { "Error fetching resource #{@url}: #{e}" }
nil
end

View File

@ -115,7 +115,7 @@ class ImportService < BaseService
next if status.nil? && ActivityPub::TagManager.instance.local_uri?(uri)
status || ActivityPub::FetchRemoteStatusService.new.call(uri)
rescue HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::UnexpectedResponseError
rescue *Mastodon::HTTP_CONNECTION_ERRORS, Mastodon::UnexpectedResponseError
nil
rescue => e
Rails.logger.warn "Unexpected error when importing bookmark: #{e}"

View File

@ -44,7 +44,7 @@ class ProcessMentionsService < BaseService
if mention_undeliverable?(mentioned_account)
begin
mentioned_account = ResolveAccountService.new.call(Regexp.last_match(1))
rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::UnexpectedResponseError
rescue Webfinger::Error, *Mastodon::HTTP_CONNECTION_ERRORS, Mastodon::UnexpectedResponseError
mentioned_account = nil
end
end

View File

@ -22,7 +22,7 @@ class SoftwareUpdateCheckService < BaseService
Request.new(:get, "#{api_url}?version=#{version}").add_headers('Accept' => 'application/json', 'User-Agent' => 'Mastodon update checker').perform do |res|
return Oj.load(res.body_with_limit, mode: :strict) if res.code == 200
end
rescue HTTP::Error, OpenSSL::SSL::SSLError, Oj::ParseError
rescue *Mastodon::HTTP_CONNECTION_ERRORS, Oj::ParseError
nil
end

View File

@ -10,7 +10,7 @@ class VerifyLinkService < BaseService
return unless link_back_present?
field.mark_verified!
rescue OpenSSL::SSL::SSLError, HTTP::Error, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, IPAddr::AddressFamilyError => e
rescue *Mastodon::HTTP_CONNECTION_ERRORS, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, IPAddr::AddressFamilyError => e
Rails.logger.debug { "Error fetching link #{@url}: #{e}" }
nil
end

View File

@ -21,7 +21,7 @@ class RefollowWorker
# Schedule re-follow
begin
FollowService.new.call(follower, target_account, reblogs: reblogs, notify: notify, languages: languages, bypass_limit: true)
rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound, Mastodon::UnexpectedResponseError, HTTP::Error, OpenSSL::SSL::SSLError
rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound, Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
next
end
end

View File

@ -55,12 +55,8 @@ class Web::PushNotificationWorker
end
def push_notification_json
Oj.dump(serialized_notification_in_subscription_locale.as_json)
end
def serialized_notification_in_subscription_locale
I18n.with_locale(@subscription.locale.presence || I18n.default_locale) do
serialized_notification
Oj.dump(serialized_notification.as_json)
end
end

View File

@ -96,10 +96,6 @@ module Mastodon
config.middleware.use Rack::Attack
config.middleware.use Mastodon::RackMiddleware
initializer :deprecator do |app|
app.deprecators[:mastodon] = ActiveSupport::Deprecation.new('4.3', 'mastodon/mastodon')
end
config.before_configuration do
require 'mastodon/redis_configuration'
::REDIS_CONFIGURATION = Mastodon::RedisConfiguration.new

View File

@ -1,56 +0,0 @@
# frozen_string_literal: true
# Some migrations have been present in glitch-soc for a long time and have then
# been merged in upstream Mastodon, under a different version number.
#
# This puts us in an uneasy situation in which if we remove upstream's
# migration file, people migrating from upstream will end up having a conflict
# with their already-ran migration.
#
# On the other hand, if we keep upstream's migration and remove our own,
# any current glitch-soc user will have a conflict during migration.
#
# For lack of a better solution, as those migrations are indeed identical,
# we decided monkey-patching Rails' Migrator to completely ignore the duplicate,
# keeping only the one that has run, or an arbitrary one.
ALLOWED_DUPLICATES = [2018_04_10_220657, 2018_08_31_171112].freeze
module ActiveRecord
class Migrator
def self.new(direction, migrations, schema_migration, internal_metadata, target_version = nil)
migrated = Set.new(Base.connection.migration_context.get_all_versions)
migrations.group_by(&:name).each_value do |duplicates|
next unless duplicates.length > 1 && duplicates.all? { |m| ALLOWED_DUPLICATES.include?(m.version) }
# We have a set of allowed duplicates. Keep the migrated one, if any.
non_migrated = duplicates.reject { |m| migrated.include?(m.version.to_i) }
migrations = begin
if duplicates.length == non_migrated.length || non_migrated.empty?
# There weren't any migrated one, so we have to pick one “canonical” migration
migrations - duplicates[1..]
else
# Just reject every duplicate which hasn't been migrated yet
migrations - non_migrated
end
end
end
super
end
end
class MigrationContext
def needs_migration?
# A set of duplicated migrations is considered migrated if at least one of
# them is migrated.
migrated = get_all_versions
migrations.group_by(&:name).each_value do |duplicates|
return true unless duplicates.any? { |m| migrated.include?(m.version.to_i) }
end
false
end
end
end

View File

@ -20,6 +20,7 @@
- ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY
Run `bin/rails db:encryption:init` to generate new secrets and then assign the environment variables.
Do not change the secrets once they are set, as doing so may cause data loss and other issues that will be difficult or impossible to recover from.
MESSAGE
end

View File

@ -21,6 +21,7 @@ ca:
one: Tut
other: Tuts
posts_tab_heading: Tuts
self_follow_error: No es permet seguir el compte propi
admin:
account_actions:
action: Realitza l'acció

View File

@ -21,6 +21,7 @@ da:
one: Indlæg
other: Indlæg
posts_tab_heading: Indlæg
self_follow_error: Det er ikke tilladt at følge sin egen konto
admin:
account_actions:
action: Udfør handling

View File

@ -21,6 +21,7 @@ de:
one: Beitrag
other: Beiträge
posts_tab_heading: Beiträge
self_follow_error: Es ist nicht erlaubt, deinem eigenen Konto zu folgen
admin:
account_actions:
action: Aktion ausführen

View File

@ -21,6 +21,7 @@ eo:
one: Afiŝo
other: Mesaĝoj
posts_tab_heading: Afiŝoj
self_follow_error: Sekvi vian propran konton ne estas permesita
admin:
account_actions:
action: Plenumi agon

View File

@ -21,6 +21,7 @@ es-AR:
one: Mensaje
other: Mensajes
posts_tab_heading: Mensajes
self_follow_error: No está permitido seguir tu propia cuenta
admin:
account_actions:
action: Ejecutar acción

View File

@ -21,6 +21,7 @@ es-MX:
one: Toot
other: Toots
posts_tab_heading: Toots
self_follow_error: No se permite seguir tu propia cuenta
admin:
account_actions:
action: Realizar acción

View File

@ -21,6 +21,7 @@ es:
one: Publicación
other: Publicaciones
posts_tab_heading: Publicaciones
self_follow_error: No está permitido seguir tu propia cuenta
admin:
account_actions:
action: Realizar acción

View File

@ -21,6 +21,7 @@ fi:
one: Julkaisu
other: viestiä
posts_tab_heading: Julkaisut
self_follow_error: Oman tilisi seuraaminen ei ole sallittua
admin:
account_actions:
action: Suorita toimi

View File

@ -21,6 +21,7 @@ fo:
one: Uppslag
other: Uppsløg
posts_tab_heading: Uppsløg
self_follow_error: Tað er ikki loyvt at fylgja tíni egnu kontu
admin:
account_actions:
action: Frem atgerð

View File

@ -21,6 +21,7 @@ gl:
one: Publicación
other: Publicacións
posts_tab_heading: Publicacións
self_follow_error: Non está permitido seguir a túa propia conta
admin:
account_actions:
action: Executar acción

View File

@ -25,6 +25,7 @@ he:
other: הודעות
two: הודעותיים
posts_tab_heading: הודעות
self_follow_error: בלתי אפשרי לך לעקוב אחרי חשבונך
admin:
account_actions:
action: בצע/י פעולה

View File

@ -21,6 +21,7 @@ is:
one: Færsla
other: Færslur
posts_tab_heading: Færslur
self_follow_error: Ekki er leyft að fylgjast með eigin aðgangi
admin:
account_actions:
action: Framkvæma aðgerð

View File

@ -19,6 +19,7 @@ ja:
posts:
other: 投稿
posts_tab_heading: 投稿
self_follow_error: 自分のアカウントをフォローすることはできません
admin:
account_actions:
action: アクションを実行

View File

@ -19,6 +19,7 @@ ko:
posts:
other: 게시물
posts_tab_heading: 게시물
self_follow_error: 본인의 계정을 팔로우할 수는 없습니다
admin:
account_actions:
action: 조치 취하기

View File

@ -21,6 +21,7 @@ nl:
one: Toot
other: Berichten
posts_tab_heading: Berichten
self_follow_error: Het volgen van je eigen account is niet toegestaan
admin:
account_actions:
action: Actie uitvoeren

View File

@ -21,6 +21,7 @@ nn:
one: Tut
other: Tut
posts_tab_heading: Tut
self_follow_error: Det er ikkje tillate å følgje din eigen konto
admin:
account_actions:
action: Utfør

View File

@ -25,6 +25,7 @@ pl:
one: wpis
other: Wpisów
posts_tab_heading: Wpisy
self_follow_error: Nie możesz obserwować swojego konta
admin:
account_actions:
action: Wykonaj działanie

View File

@ -21,6 +21,7 @@ pt-BR:
one: Publicação
other: Publicações
posts_tab_heading: Publicações
self_follow_error: Seguir sua conta não é permitido
admin:
account_actions:
action: Tomar uma atitude

View File

@ -21,6 +21,7 @@ tr:
one: Gönderi
other: Gönderiler
posts_tab_heading: Gönderiler
self_follow_error: Kendi hesabınızı takip etmenize izin yok
admin:
account_actions:
action: Eylemi gerçekleştir

View File

@ -25,6 +25,7 @@ uk:
one: Допис
other: Дописів
posts_tab_heading: Дописів
self_follow_error: Ви не можете стежити за власним обліковим записом
admin:
account_actions:
action: Виконати дію

View File

@ -19,6 +19,7 @@ vi:
posts:
other: Tút
posts_tab_heading: Tút
self_follow_error: Bạn không thể tự theo dõi chính bạn
admin:
account_actions:
action: Thực hiện hành động

View File

@ -19,6 +19,7 @@ zh-CN:
posts:
other: 嘟文
posts_tab_heading: 嘟文
self_follow_error: 不可以关注自己
admin:
account_actions:
action: 执行操作

View File

@ -19,6 +19,7 @@ zh-TW:
posts:
other: 嘟文
posts_tab_heading: 嘟文
self_follow_error: 無法跟隨您自己的帳號
admin:
account_actions:
action: 執行動作

View File

@ -1,10 +1,11 @@
# frozen_string_literal: true
# This migration is a duplicate of 20180831171112 and may get ignored, see
# config/initializers/0_duplicate_migrations.rb
# This migration is a duplicate of 20180831171112
class GlitchCreateBookmarks < ActiveRecord::Migration[5.1]
def up
return if table_exists?(:bookmarks)
class CreateBookmarks < ActiveRecord::Migration[5.1]
def change
create_table :bookmarks do |t|
t.references :account, null: false
t.references :status, null: false
@ -19,4 +20,6 @@ class CreateBookmarks < ActiveRecord::Migration[5.1]
add_index :bookmarks, [:account_id, :status_id], unique: true
end
def down; end
end

View File

@ -1,10 +1,11 @@
# frozen_string_literal: true
# This migration is a duplicate of 20180410220657 and may get ignored, see
# config/initializers/0_duplicate_migrations.rb
# This migration is a duplicate of 20180410220657
class CreateBookmarks < ActiveRecord::Migration[5.2]
def change
def up
return if table_exists?(:bookmarks)
create_table :bookmarks do |t|
t.references :account, null: false
t.references :status, null: false
@ -19,4 +20,8 @@ class CreateBookmarks < ActiveRecord::Migration[5.2]
add_index :bookmarks, [:account_id, :status_id], unique: true
end
def down
drop_table :bookmarks
end
end

View File

@ -59,7 +59,7 @@ services:
web:
# You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes
# build: .
image: ghcr.io/mastodon/mastodon:v4.3.0-rc.1
image: ghcr.io/mastodon/mastodon:v4.3.0
restart: always
env_file: .env.production
command: bundle exec puma -C config/puma.rb
@ -83,7 +83,7 @@ services:
# build:
# dockerfile: ./streaming/Dockerfile
# context: .
image: ghcr.io/mastodon/mastodon-streaming:v4.3.0-rc.1
image: ghcr.io/mastodon/mastodon-streaming:v4.3.0
restart: always
env_file: .env.production
command: node ./streaming/index.js
@ -101,7 +101,7 @@ services:
sidekiq:
build: .
image: ghcr.io/mastodon/mastodon:v4.3.0-rc.1
image: ghcr.io/mastodon/mastodon:v4.3.0
restart: always
env_file: .env.production
command: bundle exec sidekiq

View File

@ -36,4 +36,11 @@ module Mastodon
super()
end
end
HTTP_CONNECTION_ERRORS = [
HTTP::ConnectionError,
HTTP::Error,
HTTP::TimeoutError,
OpenSSL::SSL::SSLError,
].freeze
end

View File

@ -305,7 +305,7 @@ module Mastodon::CLI
begin
code = Request.new(:head, account.uri).perform(&:code)
rescue HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, Mastodon::PrivateNetworkAddressError
rescue *Mastodon::HTTP_CONNECTION_ERRORS, Mastodon::PrivateNetworkAddressError
skip_domains << account.domain
end

View File

@ -5,7 +5,7 @@ require_relative 'base'
module Mastodon::CLI
class IpBlocks < Base
option :severity, required: true, enum: %w(no_access sign_up_requires_approval sign_up_block), desc: 'Severity of the block'
option :severity, required: true, enum: IpBlock.severities.keys, desc: 'Severity of the block'
option :comment, aliases: [:c], desc: 'Optional comment'
option :duration, aliases: [:d], type: :numeric, desc: 'Duration of the block in seconds'
option :force, type: :boolean, aliases: [:f], desc: 'Overwrite existing blocks'

View File

@ -7,8 +7,19 @@ namespace :db do
namespace :encryption do
desc 'Generate a set of keys for configuring Active Record encryption in a given environment'
task :init do # rubocop:disable Rails/RakeEnvironment
if %w(
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY
).any? { |key| ENV.key?(key) }
pastel = Pastel.new
puts pastel.red(<<~MSG)
WARNING: It looks like encryption secrets have already been set. Please ensure you are not changing secrets for a Mastodon installation that already uses them, as this will cause data loss and other issues that are difficult to recover from.
MSG
end
puts <<~MSG
Add these secret environment variables to your Mastodon environment (e.g. .env.production):#{' '}
Add the following secret environment variables to your Mastodon environment (e.g. .env.production), ensure they are shared across all your nodes and do not change them after they are set:#{' '}
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=#{SecureRandom.alphanumeric(32)}
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=#{SecureRandom.alphanumeric(32)}

View File

@ -1,82 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Admin::TagsController do
render_views
before do
sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin'))
end
describe 'GET #index' do
before do
Fabricate(:tag)
tag_filter = instance_double(Admin::TagFilter, results: Tag.all)
allow(Admin::TagFilter).to receive(:new).and_return(tag_filter)
end
let(:params) { { order: 'newest' } }
it 'returns http success' do
get :index
expect(response).to have_http_status(200)
expect(response).to render_template(:index)
expect(Admin::TagFilter)
.to have_received(:new)
.with(hash_including(params))
end
describe 'with filters' do
let(:params) { { order: 'newest', name: 'test' } }
it 'returns http success' do
get :index, params: { name: 'test' }
expect(response).to have_http_status(200)
expect(response).to render_template(:index)
expect(Admin::TagFilter)
.to have_received(:new)
.with(hash_including(params))
end
end
end
describe 'GET #show' do
let!(:tag) { Fabricate(:tag) }
before do
get :show, params: { id: tag.id }
end
it 'returns status 200' do
expect(response).to have_http_status(200)
end
end
describe 'PUT #update' do
let!(:tag) { Fabricate(:tag, listable: false) }
context 'with valid params' do
it 'updates the tag' do
put :update, params: { id: tag.id, tag: { listable: '1' } }
expect(response).to redirect_to(admin_tag_path(tag.id))
expect(tag.reload).to be_listable
end
end
context 'with invalid params' do
it 'does not update the tag' do
put :update, params: { id: tag.id, tag: { name: 'cant-change-name' } }
expect(response).to have_http_status(200)
expect(response).to render_template(:show)
end
end
end
end

View File

@ -3,14 +3,13 @@
require 'rails_helper'
RSpec.describe DomainBlock do
describe 'validations' do
describe 'Validations' do
it { is_expected.to validate_presence_of(:domain) }
it 'is invalid if the same normalized domain already exists' do
_domain_block = Fabricate(:domain_block, domain: 'にゃん')
domain_block_with_normalized_value = Fabricate.build(:domain_block, domain: 'xn--r9j5b5b')
domain_block_with_normalized_value.valid?
expect(domain_block_with_normalized_value).to model_have_error_on_field(:domain)
context 'when a normalized domain exists' do
before { Fabricate(:domain_block, domain: 'にゃん') }
it { is_expected.to_not allow_value('xn--r9j5b5b').for(:domain) }
end
end
@ -105,4 +104,26 @@ RSpec.describe DomainBlock do
end
end
end
describe '#policies' do
subject { domain_block.policies }
context 'when severity is suspend' do
let(:domain_block) { Fabricate.build :domain_block, severity: :suspend }
it { is_expected.to eq(%i(suspend)) }
end
context 'when severity is noop' do
let(:domain_block) { Fabricate.build :domain_block, severity: :noop, reject_media: true }
it { is_expected.to eq(%i(reject_media)) }
end
context 'when severity is silence' do
let(:domain_block) { Fabricate.build :domain_block, severity: :silence, reject_reports: true }
it { is_expected.to eq(%i(silence reject_reports)) }
end
end
end

View File

@ -7,19 +7,25 @@ RSpec.describe NotificationPolicy do
subject { Fabricate(:notification_policy) }
let(:sender) { Fabricate(:account) }
let(:suspended_sender) { Fabricate(:account) }
before do
Fabricate.times(2, :notification, account: subject.account, activity: Fabricate(:status, account: sender), filtered: true, type: :mention)
Fabricate(:notification_request, account: subject.account, from_account: sender)
Fabricate(:notification, account: subject.account, activity: Fabricate(:status, account: suspended_sender), filtered: true, type: :mention)
Fabricate(:notification_request, account: subject.account, from_account: suspended_sender)
suspended_sender.suspend!
subject.summarize!
end
it 'sets pending_requests_count' do
expect(subject.pending_requests_count).to eq 1
end
it 'sets pending_notifications_count' do
expect(subject.pending_notifications_count).to eq 2
it 'sets pending_requests_count and pending_notifications_count' do
expect(subject).to have_attributes(
pending_requests_count: 1,
pending_notifications_count: 2
)
end
end
end

View File

@ -559,11 +559,53 @@ RSpec.describe Status do
end
end
describe 'validation' do
it 'disallow empty uri for remote status' do
alice.update(domain: 'example.com')
status = Fabricate.build(:status, uri: '', account: alice)
expect(status).to model_have_error_on_field(:uri)
describe 'Validations' do
context 'with a remote account' do
subject { Fabricate.build :status, account: remote_account }
let(:remote_account) { Fabricate :account, domain: 'example.com' }
it { is_expected.to_not allow_value('').for(:uri) }
end
end
describe 'Callbacks' do
describe 'Stripping content when required' do
context 'with a remote account' do
subject { Fabricate.build :status, local: false, account:, text: ' text ', spoiler_text: ' spoiler ' }
let(:account) { Fabricate.build :account, domain: 'host.example' }
it 'preserves content' do
expect { subject.valid? }
.to not_change(subject, :text)
.and not_change(subject, :spoiler_text)
end
end
context 'with a local account' do
let(:account) { Fabricate.build :account, domain: nil }
context 'with populated fields' do
subject { Fabricate.build :status, local: true, account:, text: ' text ', spoiler_text: ' spoiler ' }
it 'strips content' do
expect { subject.valid? }
.to change(subject, :text).to('text')
.and change(subject, :spoiler_text).to('spoiler')
end
end
context 'with empty fields' do
subject { Fabricate.build :status, local: true, account:, text: nil, spoiler_text: nil }
it 'preserves content' do
expect { subject.valid? }
.to not_change(subject, :text)
.and not_change(subject, :spoiler_text)
end
end
end
end
end

View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Admin Tags' do
describe 'Tag interaction' do
let!(:tag) { Fabricate(:tag, name: 'test') }
before { sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
it 'allows tags listing and editing' do
visit admin_tags_path
expect(page)
.to have_title(I18n.t('admin.tags.title'))
click_on '#test'
fill_in display_name_field, with: 'NewTagName'
expect { click_on submit_button }
.to_not(change { tag.reload.display_name })
expect(page)
.to have_content(match_error_text)
fill_in display_name_field, with: 'TEST'
expect { click_on submit_button }
.to(change { tag.reload.display_name }.to('TEST'))
end
def display_name_field
I18n.t('simple_form.labels.defaults.display_name')
end
def match_error_text
I18n.t('tags.does_not_match_previous_name')
end
end
end

View File

@ -24,28 +24,28 @@ RSpec.describe 'Using OAuth from an external app' do
subject
# It presents the user with an authorization page
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
# Upon authorizing, it redirects to the apps' callback URL
click_on I18n.t('doorkeeper.authorizations.buttons.authorize')
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
expect(page).to have_content(oauth_authorize_text)
# It grants the app access to the account
expect(Doorkeeper::AccessGrant.exists?(application: client_app, resource_owner_id: user.id)).to be true
expect { click_on oauth_authorize_text }
.to change { user_has_grant_with_client_app? }.to(true)
# Upon authorizing, it redirects to the apps' callback URL
expect(page).to redirect_to_callback_url
end
it 'when rejecting the authorization request' do
subject
# It presents the user with an authorization page
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.deny'))
# Upon denying, it redirects to the apps' callback URL
click_on I18n.t('doorkeeper.authorizations.buttons.deny')
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
expect(page).to have_content(oauth_deny_text)
# It does not grant the app access to the account
expect(Doorkeeper::AccessGrant.exists?(application: client_app, resource_owner_id: user.id)).to be false
expect { click_on oauth_deny_text }
.to_not change { user_has_grant_with_client_app? }.from(false)
# Upon denying, it redirects to the apps' callback URL
expect(page).to redirect_to_callback_url
end
# The tests in this context ensures that requests without PKCE parameters
@ -133,7 +133,6 @@ RSpec.describe 'Using OAuth from an external app' do
end
it 'when accepting the authorization request' do
params = { client_id: client_app.uid, response_type: 'code', redirect_uri: client_app.redirect_uri, scope: 'read' }
visit "/oauth/authorize?#{params.to_query}"
# It presents the user with a log-in page
@ -145,18 +144,17 @@ RSpec.describe 'Using OAuth from an external app' do
# Logging in redirects to an authorization page
fill_in_auth_details(email, password)
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
# Upon authorizing, it redirects to the apps' callback URL
click_on I18n.t('doorkeeper.authorizations.buttons.authorize')
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
expect(page).to have_content(oauth_authorize_text)
# It grants the app access to the account
expect(Doorkeeper::AccessGrant.exists?(application: client_app, resource_owner_id: user.id)).to be true
expect { click_on oauth_authorize_text }
.to change { user_has_grant_with_client_app? }.to(true)
# Upon authorizing, it redirects to the apps' callback URL
expect(page).to redirect_to_callback_url
end
it 'when rejecting the authorization request' do
params = { client_id: client_app.uid, response_type: 'code', redirect_uri: client_app.redirect_uri, scope: 'read' }
visit "/oauth/authorize?#{params.to_query}"
# It presents the user with a log-in page
@ -168,21 +166,20 @@ RSpec.describe 'Using OAuth from an external app' do
# Logging in redirects to an authorization page
fill_in_auth_details(email, password)
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
# Upon denying, it redirects to the apps' callback URL
click_on I18n.t('doorkeeper.authorizations.buttons.deny')
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
expect(page).to have_content(oauth_authorize_text)
# It does not grant the app access to the account
expect(Doorkeeper::AccessGrant.exists?(application: client_app, resource_owner_id: user.id)).to be false
expect { click_on oauth_deny_text }
.to_not change { user_has_grant_with_client_app? }.from(false)
# Upon denying, it redirects to the apps' callback URL
expect(page).to redirect_to_callback_url
end
context 'when the user has set up TOTP' do
let(:user) { Fabricate(:user, email: email, password: password, otp_required_for_login: true, otp_secret: User.generate_otp_secret) }
it 'when accepting the authorization request' do
params = { client_id: client_app.uid, response_type: 'code', redirect_uri: client_app.redirect_uri, scope: 'read' }
visit "/oauth/authorize?#{params.to_query}"
# It presents the user with a log-in page
@ -202,18 +199,17 @@ RSpec.describe 'Using OAuth from an external app' do
# Filling in the correct TOTP code redirects to an app authorization page
fill_in_otp_details(user.current_otp)
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
# Upon authorizing, it redirects to the apps' callback URL
click_on I18n.t('doorkeeper.authorizations.buttons.authorize')
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
expect(page).to have_content(oauth_authorize_text)
# It grants the app access to the account
expect(Doorkeeper::AccessGrant.exists?(application: client_app, resource_owner_id: user.id)).to be true
expect { click_on oauth_authorize_text }
.to change { user_has_grant_with_client_app? }.to(true)
# Upon authorizing, it redirects to the apps' callback URL
expect(page).to redirect_to_callback_url
end
it 'when rejecting the authorization request' do
params = { client_id: client_app.uid, response_type: 'code', redirect_uri: client_app.redirect_uri, scope: 'read' }
visit "/oauth/authorize?#{params.to_query}"
# It presents the user with a log-in page
@ -233,14 +229,14 @@ RSpec.describe 'Using OAuth from an external app' do
# Filling in the correct TOTP code redirects to an app authorization page
fill_in_otp_details(user.current_otp)
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
# Upon denying, it redirects to the apps' callback URL
click_on I18n.t('doorkeeper.authorizations.buttons.deny')
expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true)
expect(page).to have_content(oauth_authorize_text)
# It does not grant the app access to the account
expect(Doorkeeper::AccessGrant.exists?(application: client_app, resource_owner_id: user.id)).to be false
expect { click_on oauth_deny_text }
.to_not change { user_has_grant_with_client_app? }.from(false)
# Upon denying, it redirects to the apps' callback URL
expect(page).to redirect_to_callback_url
end
end
# TODO: external auth
@ -252,4 +248,24 @@ RSpec.describe 'Using OAuth from an external app' do
fill_in 'user_otp_attempt', with: value
click_on I18n.t('auth.login')
end
def oauth_authorize_text
I18n.t('doorkeeper.authorizations.buttons.authorize')
end
def oauth_deny_text
I18n.t('doorkeeper.authorizations.buttons.deny')
end
def redirect_to_callback_url
have_current_path(/\A#{client_app.redirect_uri}/, url: true)
end
def user_has_grant_with_client_app?
Doorkeeper::AccessGrant
.exists?(
application: client_app,
resource_owner_id: user.id
)
end
end

532
yarn.lock
View File

@ -1533,13 +1533,13 @@ __metadata:
languageName: node
linkType: hard
"@csstools/cascade-layer-name-parser@npm:^2.0.1":
version: 2.0.1
resolution: "@csstools/cascade-layer-name-parser@npm:2.0.1"
"@csstools/cascade-layer-name-parser@npm:^2.0.2":
version: 2.0.2
resolution: "@csstools/cascade-layer-name-parser@npm:2.0.2"
peerDependencies:
"@csstools/css-parser-algorithms": ^3.0.1
"@csstools/css-tokenizer": ^3.0.1
checksum: 10c0/e1f9030285d1a16ca0424018289e5288e58dee2d6f6cc392e27d9e8eca0deeea4ced9b749eef09a8322746cb15b6304bc7e2d04bb9dc7405c29b3717ec80e736
"@csstools/css-parser-algorithms": ^3.0.2
"@csstools/css-tokenizer": ^3.0.2
checksum: 10c0/2cc840445328400bb3e1e4186e6081e6519a23d9abde36a16c95892b6ad75155b3af3410d79fdda1c53a068384f970cabff4b5f5ba6867578168cbd3419016c8
languageName: node
linkType: hard
@ -1550,42 +1550,42 @@ __metadata:
languageName: node
linkType: hard
"@csstools/css-calc@npm:^2.0.1":
version: 2.0.1
resolution: "@csstools/css-calc@npm:2.0.1"
"@csstools/css-calc@npm:^2.0.2":
version: 2.0.2
resolution: "@csstools/css-calc@npm:2.0.2"
peerDependencies:
"@csstools/css-parser-algorithms": ^3.0.1
"@csstools/css-tokenizer": ^3.0.1
checksum: 10c0/84c0ba3dac51466c9ac22c3540360f6058cf351a3676d71d4412584b165a91abc7c69a4ddf5a5dacc6e6082806d2317cf50ddbc0a0562275b2aedaee41cb87a9
"@csstools/css-parser-algorithms": ^3.0.2
"@csstools/css-tokenizer": ^3.0.2
checksum: 10c0/b36e655b4abc8ea39b300725e33cd43b1875d759dd60bee8155bf7841065615a7f24cf53199382e30aa10bb137f64021043e4af7e11b7199b674a6e6cf3ccd01
languageName: node
linkType: hard
"@csstools/css-color-parser@npm:^3.0.2":
version: 3.0.2
resolution: "@csstools/css-color-parser@npm:3.0.2"
"@csstools/css-color-parser@npm:^3.0.3":
version: 3.0.3
resolution: "@csstools/css-color-parser@npm:3.0.3"
dependencies:
"@csstools/color-helpers": "npm:^5.0.1"
"@csstools/css-calc": "npm:^2.0.1"
"@csstools/css-calc": "npm:^2.0.2"
peerDependencies:
"@csstools/css-parser-algorithms": ^3.0.1
"@csstools/css-tokenizer": ^3.0.1
checksum: 10c0/31f42cc22426c937f5c6999889f72b40aec8504189a6badf3319552f27a5af73dd5f46be9ebc54eb87e526468eb2546ffbd60a6879fe599efa6c98e51d6cfa3d
"@csstools/css-parser-algorithms": ^3.0.2
"@csstools/css-tokenizer": ^3.0.2
checksum: 10c0/02367ffc222254132c47f9cbc856f65fe0b81ee4a5e7381251b95c4064138b5ed99a5e5a79c0c8689f9e75e3d900f94773258a161a97f467c3f0420838c10e04
languageName: node
linkType: hard
"@csstools/css-parser-algorithms@npm:^3.0.1":
version: 3.0.1
resolution: "@csstools/css-parser-algorithms@npm:3.0.1"
"@csstools/css-parser-algorithms@npm:^3.0.1, @csstools/css-parser-algorithms@npm:^3.0.2":
version: 3.0.2
resolution: "@csstools/css-parser-algorithms@npm:3.0.2"
peerDependencies:
"@csstools/css-tokenizer": ^3.0.1
checksum: 10c0/064c6d519197b5af43bbf5efe8f4cdbd361b006113aa82160d637e925b50c643a52d33d512ca01c63042d952d723a2a10798231a714668356b76668fb11294e3
"@csstools/css-tokenizer": ^3.0.2
checksum: 10c0/246afbf518ee9eaa24ed7f083360eb66884f1172fd4f8c663bff8c6099de2a8abd1e2a31d5b6fe42e010277d238469d780cff62bc7fdc6a52e7a90626b8924dc
languageName: node
linkType: hard
"@csstools/css-tokenizer@npm:^3.0.1":
version: 3.0.1
resolution: "@csstools/css-tokenizer@npm:3.0.1"
checksum: 10c0/c9ed4373e5731b5375ea9791590081019c04e95f08b46b272977e5e7b8c3d560affc62e82263cb8def1df1e57f0673140e7e16a14a5e7be04e6a234be088d1d3
"@csstools/css-tokenizer@npm:^3.0.1, @csstools/css-tokenizer@npm:^3.0.2":
version: 3.0.2
resolution: "@csstools/css-tokenizer@npm:3.0.2"
checksum: 10c0/a74e5829420ed35982fd33be272c2a19cb2380179d357abe750aa848be6d6699d0437008f47a57eb7c6ff64a34b0c8f91a97dd63dbddd08249b7cf7983767e5e
languageName: node
linkType: hard
@ -1599,6 +1599,16 @@ __metadata:
languageName: node
linkType: hard
"@csstools/media-query-list-parser@npm:^4.0.0":
version: 4.0.0
resolution: "@csstools/media-query-list-parser@npm:4.0.0"
peerDependencies:
"@csstools/css-parser-algorithms": ^3.0.2
"@csstools/css-tokenizer": ^3.0.2
checksum: 10c0/416417bcfd84c18a2df8dc77f31c87830e151dc20530fe7f0d8f13a0848b1a9090858abdf7792d82bf2edb41ddedb7b57b34eb78b68b5c10755ae02c019e496a
languageName: node
linkType: hard
"@csstools/postcss-cascade-layers@npm:^5.0.0":
version: 5.0.0
resolution: "@csstools/postcss-cascade-layers@npm:5.0.0"
@ -1611,60 +1621,60 @@ __metadata:
languageName: node
linkType: hard
"@csstools/postcss-color-function@npm:^4.0.2":
version: 4.0.2
resolution: "@csstools/postcss-color-function@npm:4.0.2"
"@csstools/postcss-color-function@npm:^4.0.3":
version: 4.0.3
resolution: "@csstools/postcss-color-function@npm:4.0.3"
dependencies:
"@csstools/css-color-parser": "npm:^3.0.2"
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/css-color-parser": "npm:^3.0.3"
"@csstools/css-parser-algorithms": "npm:^3.0.2"
"@csstools/css-tokenizer": "npm:^3.0.2"
"@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
"@csstools/utilities": "npm:^2.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/c987ccc7ab326668895396d3fe69c05087cf6e245be6f70e8c33da0cdeb56f8ac3d117cfad984191ec57be9691ab56d427aaa28c61c4a7446521972993dd5039
checksum: 10c0/c994660ca0e2652755d9ad181c8cb46a07220c972086c97c843fa9bacf690be10c642770f898aeec4acc47c2b718dfc7372107285a678361f34d84d9e9c11e0c
languageName: node
linkType: hard
"@csstools/postcss-color-mix-function@npm:^3.0.2":
version: 3.0.2
resolution: "@csstools/postcss-color-mix-function@npm:3.0.2"
"@csstools/postcss-color-mix-function@npm:^3.0.3":
version: 3.0.3
resolution: "@csstools/postcss-color-mix-function@npm:3.0.3"
dependencies:
"@csstools/css-color-parser": "npm:^3.0.2"
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/css-color-parser": "npm:^3.0.3"
"@csstools/css-parser-algorithms": "npm:^3.0.2"
"@csstools/css-tokenizer": "npm:^3.0.2"
"@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
"@csstools/utilities": "npm:^2.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/cfdc641f504f9d02b9a7b53d6bd933d4e767ecaee5f3d2df45d897f69b8a38b5b79b538d307b16fb56dcb7c19dc7e107518c356772e89771e28e04fd846d9035
checksum: 10c0/4ba358eb9030fc485bfe2922d897eeb712725762cc399eaba60ba665c84dc3e56a4d5a52dfb320093c0b217d32fedb9b5197fa45738cade53d9afcbefdadf04f
languageName: node
linkType: hard
"@csstools/postcss-content-alt-text@npm:^2.0.1":
version: 2.0.1
resolution: "@csstools/postcss-content-alt-text@npm:2.0.1"
"@csstools/postcss-content-alt-text@npm:^2.0.2":
version: 2.0.2
resolution: "@csstools/postcss-content-alt-text@npm:2.0.2"
dependencies:
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/css-parser-algorithms": "npm:^3.0.2"
"@csstools/css-tokenizer": "npm:^3.0.2"
"@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
"@csstools/utilities": "npm:^2.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/693e4cfa9a30a9c384120bd24846a7cd5ba1e1ebf975eb81b0e0131a21ac28a0301c7dcfa13706bc7d8b5343536fb43a46de636c3257d0fd05041c7255366e87
checksum: 10c0/e52d40f6567b9b23b32a6c40f9b2a74d57f99a9921b4cae015f51f72453474236c760bb13120682f8815698a615e0ad7bed22314c29dca89c34b5480d83a7a6d
languageName: node
linkType: hard
"@csstools/postcss-exponential-functions@npm:^2.0.1":
version: 2.0.1
resolution: "@csstools/postcss-exponential-functions@npm:2.0.1"
"@csstools/postcss-exponential-functions@npm:^2.0.2":
version: 2.0.2
resolution: "@csstools/postcss-exponential-functions@npm:2.0.2"
dependencies:
"@csstools/css-calc": "npm:^2.0.1"
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/css-calc": "npm:^2.0.2"
"@csstools/css-parser-algorithms": "npm:^3.0.2"
"@csstools/css-tokenizer": "npm:^3.0.2"
peerDependencies:
postcss: ^8.4
checksum: 10c0/ddcaedfa48cc0cf93611c8d2ed5a75d56c1d196a97015db644b45881adabb47f3255242acaef6ea869a1e5ba66328725d254bf6d29eb5e988cde8b040bc5f55d
checksum: 10c0/034ff89089872f63a6b00bda670c5ff11361babd221ed3e441dde969a718059e5d44ab0ed331868f137bb205331b808ecbcc4cb641d5c945238ebca28aa3ed59
languageName: node
linkType: hard
@ -1680,46 +1690,46 @@ __metadata:
languageName: node
linkType: hard
"@csstools/postcss-gamut-mapping@npm:^2.0.2":
version: 2.0.2
resolution: "@csstools/postcss-gamut-mapping@npm:2.0.2"
"@csstools/postcss-gamut-mapping@npm:^2.0.3":
version: 2.0.3
resolution: "@csstools/postcss-gamut-mapping@npm:2.0.3"
dependencies:
"@csstools/css-color-parser": "npm:^3.0.2"
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/css-color-parser": "npm:^3.0.3"
"@csstools/css-parser-algorithms": "npm:^3.0.2"
"@csstools/css-tokenizer": "npm:^3.0.2"
peerDependencies:
postcss: ^8.4
checksum: 10c0/8b6504f81c5036e3c2a9f9516c371f48a283112469b746546c8c7f6f0da2467c915d4dac6dfe8bb05d7dab3a7503911391eb9e666cb7632e09a032e801c029f5
checksum: 10c0/21f5708f63e9c3b7603f8b72b5f288e0a021e9710a6abf4aaa713ff4d04bae057d1861e1f28d7670ea39ba463ac04f1507876d4a11178934e7cc7a1c6a780084
languageName: node
linkType: hard
"@csstools/postcss-gradients-interpolation-method@npm:^5.0.2":
version: 5.0.2
resolution: "@csstools/postcss-gradients-interpolation-method@npm:5.0.2"
"@csstools/postcss-gradients-interpolation-method@npm:^5.0.3":
version: 5.0.3
resolution: "@csstools/postcss-gradients-interpolation-method@npm:5.0.3"
dependencies:
"@csstools/css-color-parser": "npm:^3.0.2"
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/css-color-parser": "npm:^3.0.3"
"@csstools/css-parser-algorithms": "npm:^3.0.2"
"@csstools/css-tokenizer": "npm:^3.0.2"
"@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
"@csstools/utilities": "npm:^2.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/4fa27437ad9861b1457c28228f2503c17bcc2f77dcb38da1314a4a5d38fd010e7e5d11b5f9d69e0a2cb2999bbfeca1e99ce2f59422bda5b382658dcb03f7326e
checksum: 10c0/062d27148438309c940a1973bfc7d42a06caa9397bf2382c7a61979f5be3d6f3fae1bc8ddf94d2dd8e6c807e0530a9e76179510266aaddc439677bf79447a765
languageName: node
linkType: hard
"@csstools/postcss-hwb-function@npm:^4.0.2":
version: 4.0.2
resolution: "@csstools/postcss-hwb-function@npm:4.0.2"
"@csstools/postcss-hwb-function@npm:^4.0.3":
version: 4.0.3
resolution: "@csstools/postcss-hwb-function@npm:4.0.3"
dependencies:
"@csstools/css-color-parser": "npm:^3.0.2"
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/css-color-parser": "npm:^3.0.3"
"@csstools/css-parser-algorithms": "npm:^3.0.2"
"@csstools/css-tokenizer": "npm:^3.0.2"
"@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
"@csstools/utilities": "npm:^2.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/46dc9596e37830de4c38f70764d6da9f2fc7bc339217b4291eced75daa8998c4e05fb743c271701e44818df4ac111c285019b7bb3a728e8b61d86899bbeb74eb
checksum: 10c0/faf2bfbafeec765391e37c7a5cbc7b4647d9ab1ffa51e922c7dfffa545c3d436d15604dfdfb9d7684e760042e62bb42e0243dd4ebd8c3c14694a9f7be4e57b30
languageName: node
linkType: hard
@ -1757,17 +1767,17 @@ __metadata:
languageName: node
linkType: hard
"@csstools/postcss-light-dark-function@npm:^2.0.4":
version: 2.0.4
resolution: "@csstools/postcss-light-dark-function@npm:2.0.4"
"@csstools/postcss-light-dark-function@npm:^2.0.5":
version: 2.0.5
resolution: "@csstools/postcss-light-dark-function@npm:2.0.5"
dependencies:
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/css-parser-algorithms": "npm:^3.0.2"
"@csstools/css-tokenizer": "npm:^3.0.2"
"@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
"@csstools/utilities": "npm:^2.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/0176422ad9747953964b1ceff002df1ecb1952ebc481db6192070d68777135b582ea6fd32ae819b9c64c96cb9170bd6907c647c85b48daa4984b7ed3d7f9bccb
checksum: 10c0/80635ee312d2a8f42aa5ce6792f1dc4a71199c384c66a3270d37e998d96db55beaa6836d689cda3b7e4828227960582fae04659ba5e4e0f64fd4543cbf15c6ab
languageName: node
linkType: hard
@ -1809,42 +1819,42 @@ __metadata:
languageName: node
linkType: hard
"@csstools/postcss-logical-viewport-units@npm:^3.0.1":
version: 3.0.1
resolution: "@csstools/postcss-logical-viewport-units@npm:3.0.1"
"@csstools/postcss-logical-viewport-units@npm:^3.0.2":
version: 3.0.2
resolution: "@csstools/postcss-logical-viewport-units@npm:3.0.2"
dependencies:
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.2"
"@csstools/utilities": "npm:^2.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/f54f91ec4d308562371576e82131c3cc1ff461a951c9a38f0b42b783c26f37a93cc846fcd025d3c4a8437b55a4fff1192ebfac8ccf84abb6478b2c515d232552
checksum: 10c0/31f525e774bc053f545a159eb53bb21465ea2930118e87c40207ad90fa97d3151e6de83efd02f84803fb0e93ed4a4b42a3904b734423410e73ac4c6ce9666ab4
languageName: node
linkType: hard
"@csstools/postcss-media-minmax@npm:^2.0.1":
version: 2.0.1
resolution: "@csstools/postcss-media-minmax@npm:2.0.1"
"@csstools/postcss-media-minmax@npm:^2.0.2":
version: 2.0.2
resolution: "@csstools/postcss-media-minmax@npm:2.0.2"
dependencies:
"@csstools/css-calc": "npm:^2.0.1"
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/media-query-list-parser": "npm:^3.0.1"
"@csstools/css-calc": "npm:^2.0.2"
"@csstools/css-parser-algorithms": "npm:^3.0.2"
"@csstools/css-tokenizer": "npm:^3.0.2"
"@csstools/media-query-list-parser": "npm:^4.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/23c1fb0c3ed8bf82f3223f161d0d65ba62045b917bc19624581f64aaa5d678485d22c23af2591a3f634ba02030ccb419c2b30209aa22f9fd2baa1a6474af810a
checksum: 10c0/83cf10742884fca3baa7ae26e2cb34123ce5a022622390566c139b4587ea8583fab00acbb85545786b36398e2201d2a94301e0fae805e55f375f1b5c38f67ce8
languageName: node
linkType: hard
"@csstools/postcss-media-queries-aspect-ratio-number-values@npm:^3.0.1":
version: 3.0.1
resolution: "@csstools/postcss-media-queries-aspect-ratio-number-values@npm:3.0.1"
"@csstools/postcss-media-queries-aspect-ratio-number-values@npm:^3.0.2":
version: 3.0.2
resolution: "@csstools/postcss-media-queries-aspect-ratio-number-values@npm:3.0.2"
dependencies:
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/media-query-list-parser": "npm:^3.0.1"
"@csstools/css-parser-algorithms": "npm:^3.0.2"
"@csstools/css-tokenizer": "npm:^3.0.2"
"@csstools/media-query-list-parser": "npm:^4.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/e491cb149fb4fff85b2f03191511e43654ae00716e3c1ea9f1dc22ec4e7042c35f034d372082a69d3621c86cafbe46e8f419872fa36f4a534f145f584d655768
checksum: 10c0/3ac4073d2e958bfb24ae45f673070dd805f0fcf07bc8d00a9a98f596d1096e7be282c8d8e87df3abde90f33fcbe2c7705e972b8c1a58e43ec44729f470b76096
languageName: node
linkType: hard
@ -1871,18 +1881,18 @@ __metadata:
languageName: node
linkType: hard
"@csstools/postcss-oklab-function@npm:^4.0.2":
version: 4.0.2
resolution: "@csstools/postcss-oklab-function@npm:4.0.2"
"@csstools/postcss-oklab-function@npm:^4.0.3":
version: 4.0.3
resolution: "@csstools/postcss-oklab-function@npm:4.0.3"
dependencies:
"@csstools/css-color-parser": "npm:^3.0.2"
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/css-color-parser": "npm:^3.0.3"
"@csstools/css-parser-algorithms": "npm:^3.0.2"
"@csstools/css-tokenizer": "npm:^3.0.2"
"@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
"@csstools/utilities": "npm:^2.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/3209a7cec6d3577544a7ef41f2d5cca25f77891d4ec0d7f39d32f9a79a6c9a9b0ee6b54b2937a2d995548ad11c39966b07d4b9f58e907ffbe1a4b454f2d277f3
checksum: 10c0/650bcb4f664308972588a8f789f806d63c4069e2e008cfc3b5c80bf9df992c62972dce279b8f434c7f78823e97095942ee4f0e37bc549258887213e72acb7ef8
languageName: node
linkType: hard
@ -1897,18 +1907,18 @@ __metadata:
languageName: node
linkType: hard
"@csstools/postcss-relative-color-syntax@npm:^3.0.2":
version: 3.0.2
resolution: "@csstools/postcss-relative-color-syntax@npm:3.0.2"
"@csstools/postcss-relative-color-syntax@npm:^3.0.3":
version: 3.0.3
resolution: "@csstools/postcss-relative-color-syntax@npm:3.0.3"
dependencies:
"@csstools/css-color-parser": "npm:^3.0.2"
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/css-color-parser": "npm:^3.0.3"
"@csstools/css-parser-algorithms": "npm:^3.0.2"
"@csstools/css-tokenizer": "npm:^3.0.2"
"@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
"@csstools/utilities": "npm:^2.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/34a8c999e08c6e7833484ee2fb91e7fcc25235d6c361712fed581e44a5a29f1ceb95415b6f4260de53809ac13f5da5415d1905c2971477cf5d45e5196081c663
checksum: 10c0/c241fe6b725d775f6d2085be1dff3868d189b176fa91ab1eb1133e30e30c8151bded4e50d17a845edd0bdd0a7adf9e8883cb2634fea3394872591fe9ad2a7e86
languageName: node
linkType: hard
@ -1923,16 +1933,16 @@ __metadata:
languageName: node
linkType: hard
"@csstools/postcss-stepped-value-functions@npm:^4.0.1":
version: 4.0.1
resolution: "@csstools/postcss-stepped-value-functions@npm:4.0.1"
"@csstools/postcss-stepped-value-functions@npm:^4.0.2":
version: 4.0.2
resolution: "@csstools/postcss-stepped-value-functions@npm:4.0.2"
dependencies:
"@csstools/css-calc": "npm:^2.0.1"
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/css-calc": "npm:^2.0.2"
"@csstools/css-parser-algorithms": "npm:^3.0.2"
"@csstools/css-tokenizer": "npm:^3.0.2"
peerDependencies:
postcss: ^8.4
checksum: 10c0/7e65969b124fce603675ca17c2ffa2bb456677866e54bc9fbdc4da0945be1593fde2abb0730d3d03190776ad2022b394a1f9d4834c5b1f4c7ec497929fd35f8f
checksum: 10c0/444a27d725bc7a8e1554469e8ac69e248ff525b728fbe058523b0f1aefcff80ca707f543d21fead0a22d51603b1669190fb01f0f2dcd599a01768a37e0d62bc3
languageName: node
linkType: hard
@ -1948,16 +1958,16 @@ __metadata:
languageName: node
linkType: hard
"@csstools/postcss-trigonometric-functions@npm:^4.0.1":
version: 4.0.1
resolution: "@csstools/postcss-trigonometric-functions@npm:4.0.1"
"@csstools/postcss-trigonometric-functions@npm:^4.0.2":
version: 4.0.2
resolution: "@csstools/postcss-trigonometric-functions@npm:4.0.2"
dependencies:
"@csstools/css-calc": "npm:^2.0.1"
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/css-calc": "npm:^2.0.2"
"@csstools/css-parser-algorithms": "npm:^3.0.2"
"@csstools/css-tokenizer": "npm:^3.0.2"
peerDependencies:
postcss: ^8.4
checksum: 10c0/59e017ebb9f4f8f027e134024e3322b5e202cc96073e0bb0d45733a829c8eadc7f4f7ce57ce8360a748a677595af9ea95da1779684699b48b911b73b4017ac8b
checksum: 10c0/eaecb2ea891162e4fcbbccf4f660c99e9e59f21937b70fe6aec3b51441eff2a12c1a2dc13fff426722629a7929919fd866311eaa68d74ee9d1f5387a23502fe2
languageName: node
linkType: hard
@ -2257,8 +2267,8 @@ __metadata:
linkType: hard
"@formatjs/cli@npm:^6.1.1":
version: 6.2.12
resolution: "@formatjs/cli@npm:6.2.12"
version: 6.2.15
resolution: "@formatjs/cli@npm:6.2.15"
peerDependencies:
"@glimmer/env": ^0.1.7
"@glimmer/reference": ^0.91.1 || ^0.92.0
@ -2287,7 +2297,7 @@ __metadata:
optional: true
bin:
formatjs: bin/formatjs
checksum: 10c0/3bd05a9fad6c837e22988e6638f426c128efa46ab80ff88cf2ad81fb3bc10cf4f228907577fc01e24c2d7d505cfabfaa69f0496d2ec8f0ab2d6b5eaccb5e475c
checksum: 10c0/e947aa7f3994251392fe15673752a8d8e3c8a30733bb49de5e617d45a327a3e1d16419e2d6b01f7ef2cbe86e2946024342d5b3301e6a8f17de3de9e2e7aedb29
languageName: node
linkType: hard
@ -3113,8 +3123,8 @@ __metadata:
linkType: hard
"@reduxjs/toolkit@npm:^2.0.1":
version: 2.2.7
resolution: "@reduxjs/toolkit@npm:2.2.7"
version: 2.2.8
resolution: "@reduxjs/toolkit@npm:2.2.8"
dependencies:
immer: "npm:^10.0.3"
redux: "npm:^5.0.1"
@ -3128,7 +3138,7 @@ __metadata:
optional: true
react-redux:
optional: true
checksum: 10c0/7761a91adac2b5e1d50a8163ba5441480bb86a3a80b7583037c27a88463394b132dd7592862fc2be03aa7ab98a6e1710549889986dc0d3f033c169a3ba2cb02e
checksum: 10c0/bf1356d71bfb82e5a181692c79c19b7bc19355260a9966f6562604c995f0cd0ce1154177ccd14095e8b319e73f64cfe86a4e46a83d24edba7876d4ae71fd5ae0
languageName: node
linkType: hard
@ -6402,10 +6412,10 @@ __metadata:
languageName: node
linkType: hard
"cookie@npm:0.6.0":
version: 0.6.0
resolution: "cookie@npm:0.6.0"
checksum: 10c0/f2318b31af7a31b4ddb4a678d024514df5e705f9be5909a192d7f116cfb6d45cbacf96a473fa733faa95050e7cff26e7832bb3ef94751592f1387b71c8956686
"cookie@npm:0.7.1":
version: 0.7.1
resolution: "cookie@npm:0.7.1"
checksum: 10c0/5de60c67a410e7c8dc8a46a4b72eb0fe925871d057c9a5d2c0e8145c4270a4f81076de83410c4d397179744b478e33cd80ccbcc457abf40a9409ad27dcd21dde
languageName: node
linkType: hard
@ -6622,10 +6632,10 @@ __metadata:
languageName: node
linkType: hard
"css-functions-list@npm:^3.2.2":
version: 3.2.2
resolution: "css-functions-list@npm:3.2.2"
checksum: 10c0/8638a63d0cf1bdc50d4a752ec1c94a57e9953c3b03eace4f5526db20bec3c061e95089f905dbb4999c44b9780ce777ba856967560f6d15119a303f6030901c10
"css-functions-list@npm:^3.2.3":
version: 3.2.3
resolution: "css-functions-list@npm:3.2.3"
checksum: 10c0/03f9ed34eeed310d2b1cf0e524eea02bc5f87854a4de85f8957ea432ab1036841a3fb00879590519f7bb8fda40d992ce7a72fa9b61696ca1dc53b90064858f96
languageName: node
linkType: hard
@ -6733,6 +6743,16 @@ __metadata:
languageName: node
linkType: hard
"css-tree@npm:^3.0.0":
version: 3.0.0
resolution: "css-tree@npm:3.0.0"
dependencies:
mdn-data: "npm:2.10.0"
source-map-js: "npm:^1.0.1"
checksum: 10c0/43d44fdf7004ae91d73d486f17894fef77efa33747a6752b9241cf0f5fb47fabc16ec34a96a993651d9014dfdeee803d7c5fcd3548214252ee19f4e5c98999b2
languageName: node
linkType: hard
"css-tree@npm:~2.2.0":
version: 2.2.1
resolution: "css-tree@npm:2.2.1"
@ -6982,15 +7002,15 @@ __metadata:
languageName: node
linkType: hard
"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:~4.3.6":
version: 4.3.6
resolution: "debug@npm:4.3.6"
"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:~4.3.6":
version: 4.3.7
resolution: "debug@npm:4.3.7"
dependencies:
ms: "npm:2.1.2"
ms: "npm:^2.1.3"
peerDependenciesMeta:
supports-color:
optional: true
checksum: 10c0/3293416bff072389c101697d4611c402a6bacd1900ac20c0492f61a9cdd6b3b29750fc7f5e299f8058469ef60ff8fb79b86395a30374fbd2490113c1c7112285
checksum: 10c0/1471db19c3b06d485a622d62f65947a19a23fbd0dd73f7fd3eafb697eec5360cde447fb075919987899b1a2096e85d35d4eb5a4de09a57600ac9cf7e6c8e768b
languageName: node
linkType: hard
@ -8413,15 +8433,15 @@ __metadata:
linkType: hard
"express@npm:^4.17.1, express@npm:^4.18.2":
version: 4.21.0
resolution: "express@npm:4.21.0"
version: 4.21.1
resolution: "express@npm:4.21.1"
dependencies:
accepts: "npm:~1.3.8"
array-flatten: "npm:1.1.1"
body-parser: "npm:1.20.3"
content-disposition: "npm:0.5.4"
content-type: "npm:~1.0.4"
cookie: "npm:0.6.0"
cookie: "npm:0.7.1"
cookie-signature: "npm:1.0.6"
debug: "npm:2.6.9"
depd: "npm:2.0.0"
@ -8447,7 +8467,7 @@ __metadata:
type-is: "npm:~1.6.18"
utils-merge: "npm:1.0.1"
vary: "npm:~1.1.2"
checksum: 10c0/4cf7ca328f3fdeb720f30ccb2ea7708bfa7d345f9cc460b64a82bf1b2c91e5b5852ba15a9a11b2a165d6089acf83457fc477dc904d59cd71ed34c7a91762c6cc
checksum: 10c0/0c287867e5f6129d3def1edd9b63103a53c40d4dc8628839d4b6827e35eb8f0de5a4656f9d85f4457eba584f9871ebb2ad26c750b36bd75d9bbb8bcebdc4892c
languageName: node
linkType: hard
@ -8591,12 +8611,12 @@ __metadata:
languageName: node
linkType: hard
"file-entry-cache@npm:^9.0.0":
version: 9.0.0
resolution: "file-entry-cache@npm:9.0.0"
"file-entry-cache@npm:^9.1.0":
version: 9.1.0
resolution: "file-entry-cache@npm:9.1.0"
dependencies:
flat-cache: "npm:^5.0.0"
checksum: 10c0/07b0a4f062dc0aa258f3e1b06ac083ea25313f5e289943e146fafdaf3315dcc031635545eea7fe98fe5598b91d6c7f48dba7a251dd7ac20108a6ebf7d00b0b1c
checksum: 10c0/4b4dbc1e972f50202b1a4430d30fd99378ef6e2a64857176abdc65c5e4730a948fb37e274478520a7bacbc70f3abba455a4b9d2c1915c53f30d11dc85d3fef5e
languageName: node
linkType: hard
@ -8948,9 +8968,9 @@ __metadata:
linkType: hard
"fuzzysort@npm:^3.0.0":
version: 3.0.2
resolution: "fuzzysort@npm:3.0.2"
checksum: 10c0/c6cdbd092a8e91ed822aeac6d4fb95559759c10602cb29f27307c1cabd01fdd384fa399f7757722435b595244efb000cd63f144104c41b8551b2faff123279cb
version: 3.1.0
resolution: "fuzzysort@npm:3.1.0"
checksum: 10c0/da9bb32de16f2a5c2c000b99031d9f4f8a01380c12d5d3b67296443a1152c55987ce3c4ddbfe97481b0e9b6f2fb77d61dceba29a93ad36ee23ef5bab6a31afb8
languageName: node
linkType: hard
@ -9670,13 +9690,20 @@ __metadata:
languageName: node
linkType: hard
"ignore@npm:^5.2.0, ignore@npm:^5.3.1, ignore@npm:^5.3.2":
"ignore@npm:^5.2.0, ignore@npm:^5.3.1":
version: 5.3.2
resolution: "ignore@npm:5.3.2"
checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337
languageName: node
linkType: hard
"ignore@npm:^6.0.2":
version: 6.0.2
resolution: "ignore@npm:6.0.2"
checksum: 10c0/9a38feac1861906a78ba0f03e8ef3cd6b0526dce2a1a84e1009324b557763afeb9c3ebcc04666b21f7bbf71adda45e76781bb9e2eaa0903d45dcaded634454f5
languageName: node
linkType: hard
"immer@npm:^10.0.3":
version: 10.0.3
resolution: "immer@npm:10.0.3"
@ -11842,6 +11869,13 @@ __metadata:
languageName: node
linkType: hard
"mdn-data@npm:2.10.0":
version: 2.10.0
resolution: "mdn-data@npm:2.10.0"
checksum: 10c0/f6f1a6a6eb092bab250d06f6f6c7cb1733a77a17e7119aac829ad67d4322bbf6a30df3c6d88686e71942e66bd49274b2ddfede22a1d3df0d6c49a56fbd09eb7c
languageName: node
linkType: hard
"media-typer@npm:0.3.0":
version: 0.3.0
resolution: "media-typer@npm:0.3.0"
@ -12221,14 +12255,7 @@ __metadata:
languageName: node
linkType: hard
"ms@npm:2.1.2":
version: 2.1.2
resolution: "ms@npm:2.1.2"
checksum: 10c0/a437714e2f90dbf881b5191d35a6db792efbca5badf112f87b9e1c712aace4b4b9b742dd6537f3edf90fd6f684de897cec230abde57e87883766712ddda297cc
languageName: node
linkType: hard
"ms@npm:2.1.3, ms@npm:^2.1.1":
"ms@npm:2.1.3, ms@npm:^2.1.1, ms@npm:^2.1.3":
version: 2.1.3
resolution: "ms@npm:2.1.3"
checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48
@ -13376,18 +13403,18 @@ __metadata:
languageName: node
linkType: hard
"postcss-color-functional-notation@npm:^7.0.2":
version: 7.0.2
resolution: "postcss-color-functional-notation@npm:7.0.2"
"postcss-color-functional-notation@npm:^7.0.3":
version: 7.0.3
resolution: "postcss-color-functional-notation@npm:7.0.3"
dependencies:
"@csstools/css-color-parser": "npm:^3.0.2"
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/css-color-parser": "npm:^3.0.3"
"@csstools/css-parser-algorithms": "npm:^3.0.2"
"@csstools/css-tokenizer": "npm:^3.0.2"
"@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
"@csstools/utilities": "npm:^2.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/e89c0bff94558b0c978ac36f7e02f7f516291f90fd43169d39c63ad2bb0415e3b1c4b3c2469280d578727e850fdf15a557230cb28275f3f0676f0f73187f2867
checksum: 10c0/5e04c81002512c960784043c096bc91ebc76b8fddb9259a2418b0e121eb65042944cc0f78946f6b7e5774ff1fee087849019655e4848af1f88879e3ab9ff7c17
languageName: node
linkType: hard
@ -13441,46 +13468,46 @@ __metadata:
languageName: node
linkType: hard
"postcss-custom-media@npm:^11.0.2":
version: 11.0.2
resolution: "postcss-custom-media@npm:11.0.2"
"postcss-custom-media@npm:^11.0.3":
version: 11.0.3
resolution: "postcss-custom-media@npm:11.0.3"
dependencies:
"@csstools/cascade-layer-name-parser": "npm:^2.0.1"
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/media-query-list-parser": "npm:^3.0.1"
"@csstools/cascade-layer-name-parser": "npm:^2.0.2"
"@csstools/css-parser-algorithms": "npm:^3.0.2"
"@csstools/css-tokenizer": "npm:^3.0.2"
"@csstools/media-query-list-parser": "npm:^4.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/7bec2b1e0b5d786c33c5715b611ffc8b9737252ee6bf77ca59255ac16f91ce614406923f43250e5c88b04f1bb050f155dc5ed4d9350dbd704c45fbd72e5a9a04
checksum: 10c0/bd3f0cf17d7422385d26afed510dc2acebb1d8c25fce13e2bbee1c49cdc7fe95ebe7f50b89ef0a88ebdd5f6826e89d99e26b905881ceff788df655670dba93d8
languageName: node
linkType: hard
"postcss-custom-properties@npm:^14.0.1":
version: 14.0.1
resolution: "postcss-custom-properties@npm:14.0.1"
"postcss-custom-properties@npm:^14.0.2":
version: 14.0.2
resolution: "postcss-custom-properties@npm:14.0.2"
dependencies:
"@csstools/cascade-layer-name-parser": "npm:^2.0.1"
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/cascade-layer-name-parser": "npm:^2.0.2"
"@csstools/css-parser-algorithms": "npm:^3.0.2"
"@csstools/css-tokenizer": "npm:^3.0.2"
"@csstools/utilities": "npm:^2.0.0"
postcss-value-parser: "npm:^4.2.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/12180a7f4a4fe2d528387346a1810b82ed870081756dcf6be226c839716ab3f6f4d6ca4c7208a07d7d84bf2c986beef6473e29964e7c2572066fca5d3b000ed5
checksum: 10c0/ea2e0cb60c558bb1afb4e601dcc64a38e1b28e5df3e47b83b858fc12d909d0e3453013e6b368fc05a7db7098ffcdc702a30a92f1a3c0ef67dfb97bf089021f1a
languageName: node
linkType: hard
"postcss-custom-selectors@npm:^8.0.1":
version: 8.0.1
resolution: "postcss-custom-selectors@npm:8.0.1"
"postcss-custom-selectors@npm:^8.0.2":
version: 8.0.2
resolution: "postcss-custom-selectors@npm:8.0.2"
dependencies:
"@csstools/cascade-layer-name-parser": "npm:^2.0.1"
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/cascade-layer-name-parser": "npm:^2.0.2"
"@csstools/css-parser-algorithms": "npm:^3.0.2"
"@csstools/css-tokenizer": "npm:^3.0.2"
postcss-selector-parser: "npm:^6.1.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/b867233b3d68fbab90afca8a776eb74196ebc3fac8988175d95118a47993c793138fec6cc580272bb35d9bd31086acbdd33ff86da0cab83ef2f08bfc1c23ecd6
checksum: 10c0/81673ffb0874f63c0f5e14315a5808259ec80ae8452aaf10d28112d30a9aaabbf61d13edb02f8be2965f44b943968c7eda051a1693da436ef157e77fcff0d752
languageName: node
linkType: hard
@ -13598,18 +13625,18 @@ __metadata:
languageName: node
linkType: hard
"postcss-lab-function@npm:^7.0.2":
version: 7.0.2
resolution: "postcss-lab-function@npm:7.0.2"
"postcss-lab-function@npm:^7.0.3":
version: 7.0.3
resolution: "postcss-lab-function@npm:7.0.3"
dependencies:
"@csstools/css-color-parser": "npm:^3.0.2"
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
"@csstools/css-color-parser": "npm:^3.0.3"
"@csstools/css-parser-algorithms": "npm:^3.0.2"
"@csstools/css-tokenizer": "npm:^3.0.2"
"@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
"@csstools/utilities": "npm:^2.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/6b2be7e762b4ccb58ea9051723d390f6732ad78bb30bfef9499139cf5e2ac160c3de31b2b005fcc30e9fced4abe1685df6cb76c99d548896bae6746105ac8520
checksum: 10c0/c50a73a9ed54b4194998c4627599d1f42074235f572edbbcdb0e00717f3ae2121dc8378d917792b281860c5650a617d923823da6f395515f610b5760d115354d
languageName: node
linkType: hard
@ -13930,38 +13957,38 @@ __metadata:
linkType: hard
"postcss-preset-env@npm:^10.0.0":
version: 10.0.6
resolution: "postcss-preset-env@npm:10.0.6"
version: 10.0.7
resolution: "postcss-preset-env@npm:10.0.7"
dependencies:
"@csstools/postcss-cascade-layers": "npm:^5.0.0"
"@csstools/postcss-color-function": "npm:^4.0.2"
"@csstools/postcss-color-mix-function": "npm:^3.0.2"
"@csstools/postcss-content-alt-text": "npm:^2.0.1"
"@csstools/postcss-exponential-functions": "npm:^2.0.1"
"@csstools/postcss-color-function": "npm:^4.0.3"
"@csstools/postcss-color-mix-function": "npm:^3.0.3"
"@csstools/postcss-content-alt-text": "npm:^2.0.2"
"@csstools/postcss-exponential-functions": "npm:^2.0.2"
"@csstools/postcss-font-format-keywords": "npm:^4.0.0"
"@csstools/postcss-gamut-mapping": "npm:^2.0.2"
"@csstools/postcss-gradients-interpolation-method": "npm:^5.0.2"
"@csstools/postcss-hwb-function": "npm:^4.0.2"
"@csstools/postcss-gamut-mapping": "npm:^2.0.3"
"@csstools/postcss-gradients-interpolation-method": "npm:^5.0.3"
"@csstools/postcss-hwb-function": "npm:^4.0.3"
"@csstools/postcss-ic-unit": "npm:^4.0.0"
"@csstools/postcss-initial": "npm:^2.0.0"
"@csstools/postcss-is-pseudo-class": "npm:^5.0.0"
"@csstools/postcss-light-dark-function": "npm:^2.0.4"
"@csstools/postcss-light-dark-function": "npm:^2.0.5"
"@csstools/postcss-logical-float-and-clear": "npm:^3.0.0"
"@csstools/postcss-logical-overflow": "npm:^2.0.0"
"@csstools/postcss-logical-overscroll-behavior": "npm:^2.0.0"
"@csstools/postcss-logical-resize": "npm:^3.0.0"
"@csstools/postcss-logical-viewport-units": "npm:^3.0.1"
"@csstools/postcss-media-minmax": "npm:^2.0.1"
"@csstools/postcss-media-queries-aspect-ratio-number-values": "npm:^3.0.1"
"@csstools/postcss-logical-viewport-units": "npm:^3.0.2"
"@csstools/postcss-media-minmax": "npm:^2.0.2"
"@csstools/postcss-media-queries-aspect-ratio-number-values": "npm:^3.0.2"
"@csstools/postcss-nested-calc": "npm:^4.0.0"
"@csstools/postcss-normalize-display-values": "npm:^4.0.0"
"@csstools/postcss-oklab-function": "npm:^4.0.2"
"@csstools/postcss-oklab-function": "npm:^4.0.3"
"@csstools/postcss-progressive-custom-properties": "npm:^4.0.0"
"@csstools/postcss-relative-color-syntax": "npm:^3.0.2"
"@csstools/postcss-relative-color-syntax": "npm:^3.0.3"
"@csstools/postcss-scope-pseudo-class": "npm:^4.0.0"
"@csstools/postcss-stepped-value-functions": "npm:^4.0.1"
"@csstools/postcss-stepped-value-functions": "npm:^4.0.2"
"@csstools/postcss-text-decoration-shorthand": "npm:^4.0.1"
"@csstools/postcss-trigonometric-functions": "npm:^4.0.1"
"@csstools/postcss-trigonometric-functions": "npm:^4.0.2"
"@csstools/postcss-unset-value": "npm:^4.0.0"
autoprefixer: "npm:^10.4.19"
browserslist: "npm:^4.23.1"
@ -13971,12 +13998,12 @@ __metadata:
cssdb: "npm:^8.1.1"
postcss-attribute-case-insensitive: "npm:^7.0.0"
postcss-clamp: "npm:^4.1.0"
postcss-color-functional-notation: "npm:^7.0.2"
postcss-color-functional-notation: "npm:^7.0.3"
postcss-color-hex-alpha: "npm:^10.0.0"
postcss-color-rebeccapurple: "npm:^10.0.0"
postcss-custom-media: "npm:^11.0.2"
postcss-custom-properties: "npm:^14.0.1"
postcss-custom-selectors: "npm:^8.0.1"
postcss-custom-media: "npm:^11.0.3"
postcss-custom-properties: "npm:^14.0.2"
postcss-custom-selectors: "npm:^8.0.2"
postcss-dir-pseudo-class: "npm:^9.0.0"
postcss-double-position-gradients: "npm:^6.0.0"
postcss-focus-visible: "npm:^10.0.0"
@ -13984,7 +14011,7 @@ __metadata:
postcss-font-variant: "npm:^5.0.0"
postcss-gap-properties: "npm:^6.0.0"
postcss-image-set-function: "npm:^7.0.0"
postcss-lab-function: "npm:^7.0.2"
postcss-lab-function: "npm:^7.0.3"
postcss-logical: "npm:^8.0.0"
postcss-nesting: "npm:^13.0.0"
postcss-opacity-percentage: "npm:^3.0.0"
@ -13996,7 +14023,7 @@ __metadata:
postcss-selector-not: "npm:^8.0.0"
peerDependencies:
postcss: ^8.4
checksum: 10c0/01660acf3b9ddf4d612a31819e9a5de9fe5383e9eddd2c130180f66ae90c5a881eb408e73454fd50e1839eae71678d738bf72073de08f9013c183b0bd9950fe5
checksum: 10c0/f789000e0504fd827e854bb0feb8b4c218d381314e4d863c5a36df925df412d0844c912952fe27892a320433640aeaff03ee94a3057b42011bf5d32b3963f333
languageName: node
linkType: hard
@ -14050,12 +14077,12 @@ __metadata:
languageName: node
linkType: hard
"postcss-safe-parser@npm:^7.0.0":
version: 7.0.0
resolution: "postcss-safe-parser@npm:7.0.0"
"postcss-safe-parser@npm:^7.0.1":
version: 7.0.1
resolution: "postcss-safe-parser@npm:7.0.1"
peerDependencies:
postcss: ^8.4.31
checksum: 10c0/4217afd8ce2809e959dc365e4675f499303cc6b91f94db06c8164422822db2d3b3124df701ee2234db4127ad05619b016bfb9c2bccae9bf9cf898a396f1632c9
checksum: 10c0/6957b10b818bd8d4664ec0e548af967f7549abedfb37f844d389571d36af681340f41f9477b9ccf34bcc7599bdef222d1d72e79c64373001fae77089fba6d965
languageName: node
linkType: hard
@ -14119,7 +14146,7 @@ __metadata:
languageName: node
linkType: hard
"postcss@npm:^8.2.15, postcss@npm:^8.4.24, postcss@npm:^8.4.41":
"postcss@npm:^8.2.15, postcss@npm:^8.4.24, postcss@npm:^8.4.47":
version: 8.4.47
resolution: "postcss@npm:8.4.47"
dependencies:
@ -16717,8 +16744,8 @@ __metadata:
linkType: hard
"stylelint@npm:^16.0.2":
version: 16.9.0
resolution: "stylelint@npm:16.9.0"
version: 16.10.0
resolution: "stylelint@npm:16.10.0"
dependencies:
"@csstools/css-parser-algorithms": "npm:^3.0.1"
"@csstools/css-tokenizer": "npm:^3.0.1"
@ -16728,17 +16755,17 @@ __metadata:
balanced-match: "npm:^2.0.0"
colord: "npm:^2.9.3"
cosmiconfig: "npm:^9.0.0"
css-functions-list: "npm:^3.2.2"
css-tree: "npm:^2.3.1"
debug: "npm:^4.3.6"
css-functions-list: "npm:^3.2.3"
css-tree: "npm:^3.0.0"
debug: "npm:^4.3.7"
fast-glob: "npm:^3.3.2"
fastest-levenshtein: "npm:^1.0.16"
file-entry-cache: "npm:^9.0.0"
file-entry-cache: "npm:^9.1.0"
global-modules: "npm:^2.0.0"
globby: "npm:^11.1.0"
globjoin: "npm:^0.1.4"
html-tags: "npm:^3.3.1"
ignore: "npm:^5.3.2"
ignore: "npm:^6.0.2"
imurmurhash: "npm:^0.1.4"
is-plain-object: "npm:^5.0.0"
known-css-properties: "npm:^0.34.0"
@ -16747,21 +16774,20 @@ __metadata:
micromatch: "npm:^4.0.8"
normalize-path: "npm:^3.0.0"
picocolors: "npm:^1.0.1"
postcss: "npm:^8.4.41"
postcss: "npm:^8.4.47"
postcss-resolve-nested-selector: "npm:^0.1.6"
postcss-safe-parser: "npm:^7.0.0"
postcss-safe-parser: "npm:^7.0.1"
postcss-selector-parser: "npm:^6.1.2"
postcss-value-parser: "npm:^4.2.0"
resolve-from: "npm:^5.0.0"
string-width: "npm:^4.2.3"
strip-ansi: "npm:^7.1.0"
supports-hyperlinks: "npm:^3.1.0"
svg-tags: "npm:^1.0.0"
table: "npm:^6.8.2"
write-file-atomic: "npm:^5.0.1"
bin:
stylelint: bin/stylelint.mjs
checksum: 10c0/d3ff9c8945c56b04a2fa16ec33d163325496d5db94b6fcb5adf74c76f7f794ac992888273f9a3317652ba8b6195168b2ffff382ca2a667a241e2ace8c9505ae2
checksum: 10c0/d07dd156c225d16c740995daacd78090f7fc317602e87bda2fca323a4ae427a8526d724f3089df3b2185df4520f987547668ceea9b30985988ccbc514034aa21
languageName: node
linkType: hard
@ -17406,22 +17432,22 @@ __metadata:
linkType: hard
"typescript@npm:5, typescript@npm:^5.0.4":
version: 5.6.2
resolution: "typescript@npm:5.6.2"
version: 5.6.3
resolution: "typescript@npm:5.6.3"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 10c0/3ed8297a8c7c56b7fec282532503d1ac795239d06e7c4966b42d4330c6cf433a170b53bcf93a130a7f14ccc5235de5560df4f1045eb7f3550b46ebed16d3c5e5
checksum: 10c0/44f61d3fb15c35359bc60399cb8127c30bae554cd555b8e2b46d68fa79d680354b83320ad419ff1b81a0bdf324197b29affe6cc28988cd6a74d4ac60c94f9799
languageName: node
linkType: hard
"typescript@patch:typescript@npm%3A5#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.0.4#optional!builtin<compat/typescript>":
version: 5.6.2
resolution: "typescript@patch:typescript@npm%3A5.6.2#optional!builtin<compat/typescript>::version=5.6.2&hash=8c6c40"
version: 5.6.3
resolution: "typescript@patch:typescript@npm%3A5.6.3#optional!builtin<compat/typescript>::version=5.6.3&hash=8c6c40"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 10c0/94eb47e130d3edd964b76da85975601dcb3604b0c848a36f63ac448d0104e93819d94c8bdf6b07c00120f2ce9c05256b8b6092d23cf5cf1c6fa911159e4d572f
checksum: 10c0/7c9d2e07c81226d60435939618c91ec2ff0b75fbfa106eec3430f0fcf93a584bc6c73176676f532d78c3594fe28a54b36eb40b3d75593071a7ec91301533ace7
languageName: node
linkType: hard
@ -17660,11 +17686,11 @@ __metadata:
linkType: hard
"use-debounce@npm:^10.0.0":
version: 10.0.3
resolution: "use-debounce@npm:10.0.3"
version: 10.0.4
resolution: "use-debounce@npm:10.0.4"
peerDependencies:
react: "*"
checksum: 10c0/351b62c565d6dce5a21ecc21fe3e1f8db74f70c81c8f7d9dbdfc2da1cb82d883578589f6146e684d91dac534bc3c8b145ab1a36fbf4d44cbb4113827508b39aa
checksum: 10c0/73494fc44b2bd58a7ec799a528fc20077c45fe2e94fedff6dcd88d136f7a39f417d77f584d5613aac615ed32aeb2ea393797ae1f7d5b2645eab57cb497a6d0cb
languageName: node
linkType: hard