Merge commit 'd730f6b0c5cfb18894d1a9e34d0aa2556dda3c62' into glitch-soc/merge-upstream

This commit is contained in:
Claire
2025-12-11 18:05:28 +01:00
32 changed files with 450 additions and 95 deletions

View File

@@ -9,7 +9,44 @@ permissions:
packages: write
jobs:
check-latest-stable:
runs-on: ubuntu-latest
outputs:
latest: ${{ steps.check.outputs.is_latest_stable }}
steps:
# Repository needs to be cloned to list branches
- name: Clone repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Check latest stable
shell: bash
id: check
run: |
ref="${GITHUB_REF#refs/tags/}"
if [[ "$ref" =~ ^v([0-9]+)\.([0-9]+)(\.[0-9]+)?$ ]]; then
current="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}"
else
echo "tag $ref is not semver"
echo "is_latest_stable=false" >> "$GITHUB_OUTPUT"
exit 0
fi
latest=$(git for-each-ref --format='%(refname:short)' "refs/remotes/origin/stable-*.*" \
| sed -E 's#^origin/stable-##' \
| sort -Vr \
| head -n1)
if [[ "$current" == "$latest" ]]; then
echo "is_latest_stable=true" >> "$GITHUB_OUTPUT"
else
echo "is_latest_stable=false" >> "$GITHUB_OUTPUT"
fi
build-image:
needs: check-latest-stable
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: Dockerfile
@@ -20,13 +57,14 @@ jobs:
# Only tag with latest when ran against the latest stable branch
# This needs to be updated after each minor version release
flavor: |
latest=${{ startsWith(github.ref, 'refs/tags/v4.5.') }}
latest=${{ needs.check-latest-stable.outputs.latest }}
tags: |
type=pep440,pattern={{raw}}
type=pep440,pattern=v{{major}}.{{minor}}
secrets: inherit
build-image-streaming:
needs: check-latest-stable
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: streaming/Dockerfile
@@ -37,7 +75,7 @@ jobs:
# Only tag with latest when ran against the latest stable branch
# This needs to be updated after each minor version release
flavor: |
latest=${{ startsWith(github.ref, 'refs/tags/v4.5.') }}
latest=${{ needs.check-latest-stable.outputs.latest }}
tags: |
type=pep440,pattern={{raw}}
type=pep440,pattern=v{{major}}.{{minor}}

View File

@@ -53,7 +53,7 @@ GEM
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
active_model_serializers (0.10.15)
active_model_serializers (0.10.16)
actionpack (>= 4.1)
activemodel (>= 4.1)
case_transform (>= 0.2)
@@ -481,7 +481,7 @@ GEM
addressable (~> 2.8)
nokogiri (~> 1.12)
omniauth (~> 2.1)
omniauth-rails_csrf_protection (2.0.0)
omniauth-rails_csrf_protection (2.0.1)
actionpack (>= 4.2)
omniauth (~> 2.0)
omniauth-saml (2.2.4)
@@ -839,7 +839,8 @@ GEM
stackprof (0.2.27)
starry (0.2.0)
base64
stoplight (5.6.0)
stoplight (5.7.0)
concurrent-ruby
zeitwerk
stringio (3.1.8)
strong_migrations (2.5.1)

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

View File

@@ -0,0 +1,45 @@
import { useCallback, useRef } from 'react';
export const InterceptStatusClicks: React.FC<{
onPreventedClick: (
clickedArea: 'account' | 'post',
event: React.MouseEvent,
) => void;
children: React.ReactNode;
}> = ({ onPreventedClick, children }) => {
const wrapperRef = useRef<HTMLDivElement>(null);
const handleClick = useCallback(
(e: React.MouseEvent) => {
const clickTarget = e.target as Element;
const allowedElementsSelector =
'.video-player, .audio-player, .media-gallery, .content-warning';
const allowedElements = wrapperRef.current?.querySelectorAll(
allowedElementsSelector,
);
const isTargetClickAllowed =
allowedElements &&
Array.from(allowedElements).some((element) => {
return clickTarget === element || element.contains(clickTarget);
});
if (!isTargetClickAllowed) {
e.preventDefault();
e.stopPropagation();
const wasAccountAreaClicked = !!clickTarget.closest(
'a.status__display-name',
);
onPreventedClick(wasAccountAreaClicked ? 'account' : 'post', e);
}
},
[onPreventedClick],
);
return (
<div ref={wrapperRef} onClickCapture={handleClick}>
{children}
</div>
);
};

View File

@@ -4,10 +4,14 @@
@typescript-eslint/no-unsafe-member-access,
@typescript-eslint/no-unsafe-call */
import type { ComponentPropsWithoutRef } from 'react';
import { useCallback } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { InterceptStatusClicks } from 'mastodon/components/status/intercept_status_clicks';
import { StatusQuoteManager } from 'mastodon/components/status_quoted';
import type { TopStatuses } from 'mastodon/models/annual_report';
import { makeGetStatus } from 'mastodon/selectors';
@@ -29,6 +33,24 @@ export const HighlightedPost: React.FC<{
statusId ? getStatus(state, { id: statusId }) : undefined,
);
const handleClick = useCallback<
ComponentPropsWithoutRef<typeof InterceptStatusClicks>['onPreventedClick']
>(
(clickedArea) => {
const link: string =
clickedArea === 'account'
? status.getIn(['account', 'url'])
: status.get('url');
if (context === 'standalone') {
window.location.href = link;
} else {
window.open(link, '_blank');
}
},
[status, context],
);
if (!status) {
return <div className={classNames(styles.box, styles.mostBoostedPost)} />;
}
@@ -72,7 +94,9 @@ export const HighlightedPost: React.FC<{
{context === 'modal' && <p>{label}</p>}
</div>
<StatusQuoteManager showActions={false} id={statusId} />
<InterceptStatusClicks onPreventedClick={handleClick}>
<StatusQuoteManager showActions={false} id={statusId} />
</InterceptStatusClicks>
</div>
);
};

View File

@@ -10,18 +10,17 @@ $mobile-breakpoint: 540px;
}
.modalWrapper {
position: absolute;
inset: 0;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
padding: 40px;
overflow-y: auto;
pointer-events: none;
scrollbar-color: var(--color-text-secondary) var(--color-bg-secondary);
@media (width < $mobile-breakpoint) {
padding-inline: 10px;
padding: 0;
}
.loading-indicator .circular-progress {
@@ -50,37 +49,51 @@ $mobile-breakpoint: 540px;
}
.wrapper {
--gradient-strength: 0.4;
box-sizing: border-box;
position: relative;
max-width: 600px;
padding: 24px;
padding-top: 40px;
contain: layout;
flex: 0 0 auto;
pointer-events: auto;
pointer-events: all;
color: var(--color-text-primary);
background: var(--color-bg-primary);
background:
radial-gradient(at 40% 87%, #240c9a99 0, transparent 50%),
radial-gradient(at 19% 10%, #6b0c9a99 0, transparent 50%),
radial-gradient(at 90% 27%, #9a0c8299 0, transparent 50%),
radial-gradient(at 16% 95%, #1e948299 0, transparent 50%),
radial-gradient(at 80% 91%, #16dae499 0, transparent 50%)
radial-gradient(
at 10% 27%,
rgba(83, 12, 154, var(--gradient-strength)) 0,
transparent 50%
),
radial-gradient(
at 91% 10%,
rgba(30, 24, 223, var(--gradient-strength)) 0,
transparent 25%
),
radial-gradient(
at 10% 91%,
rgba(22, 218, 228, var(--gradient-strength)) 0,
transparent 40%
),
radial-gradient(
at 75% 87%,
rgba(37, 31, 217, var(--gradient-strength)) 0,
transparent 20%
),
radial-gradient(
at 84% 60%,
rgba(95, 30, 148, var(--gradient-strength)) 0,
transparent 40%
)
var(--color-bg-primary);
border-radius: 40px;
@media (width < $mobile-breakpoint) {
padding-inline: 12px;
padding-bottom: 12px;
border-radius: 28px;
}
&::after {
content: '';
position: absolute;
inset: 0;
z-index: -1;
background: inherit;
border-radius: inherit;
filter: blur(20px);
border-radius: 0;
}
}
@@ -92,7 +105,7 @@ $mobile-breakpoint: 540px;
font-family: silkscreen-wrapstodon, monospace;
font-size: 28px;
line-height: 1;
margin-bottom: 8px;
margin-bottom: 4px;
padding-inline: 40px; // Prevent overlap with close button
@media (width < $mobile-breakpoint) {
@@ -116,7 +129,7 @@ $mobile-breakpoint: 540px;
.box {
position: relative;
padding: 16px;
padding: 24px;
border-radius: 16px;
background: rgb(from var(--color-bg-primary) r g b / 60%);
box-shadow: inset 0 0 0 1px rgb(from var(--color-text-primary) r g b / 40%);
@@ -150,7 +163,6 @@ $mobile-breakpoint: 540px;
flex-direction: column;
justify-content: center;
gap: 8px;
padding: 16px;
font-size: 14px;
text-align: center;
text-wrap: balance;
@@ -164,6 +176,10 @@ $mobile-breakpoint: 540px;
text-transform: uppercase;
color: #c2c8ff;
font-weight: 500;
&:last-child {
margin-bottom: -3px;
}
}
.statLarge {
@@ -185,7 +201,7 @@ $mobile-breakpoint: 540px;
.mostBoostedPost {
padding: 0;
padding-top: 8px;
padding-top: 24px;
overflow: hidden;
}
@@ -260,7 +276,7 @@ $mobile-breakpoint: 540px;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
gap: 16px;
p {
max-width: 460px;

View File

@@ -1,7 +1,7 @@
import { useCallback, useEffect, useState } from 'react';
import type { FC } from 'react';
import { defineMessage, useIntl } from 'react-intl';
import { useIntl } from 'react-intl';
import { useLocation } from 'react-router';
@@ -23,11 +23,6 @@ import { NewPosts } from './new_posts';
const moduleClassNames = classNames.bind(styles);
export const shareMessage = defineMessage({
id: 'annual_report.summary.share_message',
defaultMessage: 'I got the {archetype} archetype!',
});
export const AnnualReport: FC<{ context?: 'modal' | 'standalone' }> = ({
context = 'standalone',
}) => {

View File

@@ -1,7 +1,10 @@
import { useEffect } from 'react';
import { useCallback, useEffect } from 'react';
import classNames from 'classnames';
import { closeModal } from '@/mastodon/actions/modal';
import { useAppDispatch } from '@/mastodon/store';
import { AnnualReport } from '.';
import styles from './index.module.scss';
@@ -12,13 +15,24 @@ const AnnualReportModal: React.FC<{
onChangeBackgroundColor('var(--color-bg-media-base)');
}, [onChangeBackgroundColor]);
const dispatch = useAppDispatch();
const handleCloseModal = useCallback(() => {
dispatch(closeModal({ modalType: 'ANNUAL_REPORT', ignoreFocus: false }));
}, [dispatch]);
return (
// It's fine not to provide a keyboard handler here since there is a global
// [Esc] key listener that will close open modals.
// This onClick handler is needed since the modalWrapper styles overlap the
// default modal backdrop, preventing clicks to pass through.
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<div
className={classNames(
'modal-root__modal',
styles.modalWrapper,
'theme-dark',
)}
onClick={handleCloseModal}
>
<AnnualReport context='modal' />
</div>

View File

@@ -1,7 +1,7 @@
import { useCallback } from 'react';
import type { FC } from 'react';
import { useIntl } from 'react-intl';
import { defineMessages, useIntl } from 'react-intl';
import { resetCompose, focusCompose } from '@/mastodon/actions/compose';
import { closeModal } from '@/mastodon/actions/modal';
@@ -9,9 +9,19 @@ import { Button } from '@/mastodon/components/button';
import type { AnnualReport as AnnualReportData } from '@/mastodon/models/annual_report';
import { useAppDispatch } from '@/mastodon/store';
import { shareMessage } from '.';
import { archetypeNames } from './archetype';
const messages = defineMessages({
share_message: {
id: 'annual_report.summary.share_message',
defaultMessage: 'I got the {archetype} archetype!',
},
share_on_mastodon: {
id: 'annual_report.summary.share_on_mastodon',
defaultMessage: 'Share on Mastodon',
},
});
export const ShareButton: FC<{ report: AnnualReportData }> = ({ report }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
@@ -21,7 +31,7 @@ export const ShareButton: FC<{ report: AnnualReportData }> = ({ report }) => {
archetypeNames[report.data.archetype],
);
const shareLines = [
intl.formatMessage(shareMessage, {
intl.formatMessage(messages.share_message, {
archetype: archetypeName,
}),
];
@@ -37,5 +47,10 @@ export const ShareButton: FC<{ report: AnnualReportData }> = ({ report }) => {
dispatch(closeModal({ modalType: 'ANNUAL_REPORT', ignoreFocus: false }));
}, [report, intl, dispatch]);
return <Button text='Share here' onClick={handleShareClick} />;
return (
<Button
text={intl.formatMessage(messages.share_on_mastodon)}
onClick={handleShareClick}
/>
);
};

View File

@@ -1,7 +1,15 @@
$mobile-breakpoint: 540px;
.wrapper {
max-width: max-content;
box-sizing: border-box;
max-width: 600px;
margin-inline: auto;
padding: 40px 10px;
@media (width < $mobile-breakpoint) {
padding-top: 0;
padding-inline: 0;
}
}
.footer {

View File

@@ -5,7 +5,7 @@ import { FormattedMessage } from 'react-intl';
import { IconLogo } from '@/mastodon/components/logo';
import { AnnualReport } from './index';
import classes from './shared_page.module.css';
import classes from './shared_page.module.scss';
export const WrapstodonSharedPage: FC = () => {
return (

View File

@@ -152,6 +152,7 @@
"annual_report.summary.percentile.text": "<topLabel>That puts you in the top</topLabel><percentage></percentage><bottomLabel>of {domain} users.</bottomLabel>",
"annual_report.summary.percentile.we_wont_tell_bernie": "We won't tell Bernie.",
"annual_report.summary.share_message": "I got the {archetype} archetype!",
"annual_report.summary.share_on_mastodon": "Share on Mastodon",
"attachments_list.unprocessed": "(unprocessed)",
"audio.hide": "Hide audio",
"block_modal.remote_users_caveat": "We will ask the server {domain} to respect your decision. However, compliance is not guaranteed since some servers may handle blocks differently. Public posts may still be visible to non-logged-in users.",

View File

@@ -5,14 +5,14 @@ class AnnualReport::TopStatuses < AnnualReport::Source
{
top_statuses: {
by_reblogs: status_identifier(most_reblogged_status),
by_favourites: status_identifier(most_favourited_status),
by_replies: status_identifier(most_replied_status),
by_favourites: nil,
by_replies: nil,
},
}
end
def eligible?
report_statuses.public_visibility.exists?
report_statuses.distributable_visibility.exists?
end
private
@@ -43,7 +43,7 @@ class AnnualReport::TopStatuses < AnnualReport::Source
def base_scope
report_statuses
.public_visibility
.distributable_visibility
.joins(:status_stat)
end
end

View File

@@ -451,6 +451,10 @@ class Account < ApplicationRecord
save!
end
def featureable?
local? && discoverable?
end
private
def prepare_contents

View File

@@ -32,6 +32,8 @@ class CollectionItem < ApplicationRecord
validates :account, presence: true, if: :accepted?
validates :object_uri, presence: true, if: -> { account.nil? }
before_validation :set_position, on: :create
scope :ordered, -> { order(position: :asc) }
scope :with_accounts, -> { includes(account: [:account_stat, :user]) }
scope :not_blocked_by, ->(account) { where.not(accounts: { id: account.blocking }) }
@@ -39,4 +41,12 @@ class CollectionItem < ApplicationRecord
def local_item_with_remote_account?
local? && account&.remote?
end
private
def set_position
return if position_changed?
self.position = self.class.where(collection_id:).maximum(:position).to_i + 1
end
end

View File

@@ -64,4 +64,8 @@ class AccountPolicy < ApplicationPolicy
def review?
role.can?(:manage_taxonomies)
end
def feature?
record.featureable? && !current_account.blocking?(record) && !record.blocking?(current_account)
end
end

View File

@@ -0,0 +1,23 @@
# frozen_string_literal: true
class AddAccountToCollectionService
def call(collection, account)
raise ArgumentError unless collection.local?
@collection = collection
@account = account
raise Mastodon::NotPermittedError, I18n.t('accounts.errors.cannot_be_added_to_collections') unless AccountPolicy.new(@collection.account, @account).feature?
create_collection_item
end
private
def create_collection_item
@collection.collection_items.create!(
account: @account,
state: :accepted
)
end
end

View File

@@ -0,0 +1,6 @@
- if %w(lurker booster pollster replier oracle).include?(report.data['archetype'])
= opengraph 'og:image', frontend_asset_url("images/archetypes/previews/#{report.data['archetype']}.jpg")
= opengraph 'og:image:type', 'image/jpeg'
= opengraph 'og:image:width', 1200
= opengraph 'og:image:height', 630
= opengraph 'twitter:card', 'summary_large_image'

View File

@@ -9,6 +9,7 @@
= opengraph 'profile:username', acct(@account)[1..]
= render 'og_description', account: @account
= render 'og_image', report: @generated_annual_report
= flavoured_vite_typescript_tag 'wrapstodon.tsx', crossorigin: 'anonymous'

View File

@@ -7,6 +7,8 @@ en:
hosted_on: Mastodon hosted on %{domain}
title: About
accounts:
errors:
cannot_be_added_to_collections: This account cannot be added to collections.
followers:
one: Follower
other: Followers

View File

@@ -40,8 +40,8 @@ RSpec.describe AnnualReport::TopStatuses do
.to include(
top_statuses: include(
by_reblogs: reblogged_status.id.to_s,
by_favourites: favourited_status.id.to_s,
by_replies: replied_status.id.to_s
by_favourites: nil,
by_replies: nil
)
)
end

View File

@@ -781,4 +781,37 @@ RSpec.describe Account do
expect(subject.reload.followers_count).to eq 15
end
end
describe '#featureable?' do
subject { Fabricate.build(:account, domain: (local ? nil : 'example.com'), discoverable:) }
context 'when account is local' do
let(:local) { true }
context 'when account is discoverable' do
let(:discoverable) { true }
it 'returns `true`' do
expect(subject.featureable?).to be true
end
end
context 'when account is not discoverable' do
let(:discoverable) { false }
it 'returns `false`' do
expect(subject.featureable?).to be false
end
end
end
context 'when account is remote' do
let(:local) { false }
let(:discoverable) { true }
it 'returns `false`' do
expect(subject.featureable?).to be false
end
end
end
end

View File

@@ -38,4 +38,23 @@ RSpec.describe CollectionItem do
it { is_expected.to validate_presence_of(:object_uri) }
end
end
describe 'Creation' do
let(:collection) { Fabricate(:collection) }
let(:other_collection) { Fabricate(:collection) }
let(:account) { Fabricate(:account) }
let(:other_account) { Fabricate(:account) }
it 'automatically sets the `position` if absent' do
first_item = collection.collection_items.create(account:)
second_item = collection.collection_items.create(account: other_account)
unrelated_item = other_collection.collection_items.create(account:)
custom_item = other_collection.collection_items.create(account: other_account, position: 7)
expect(first_item.position).to eq 1
expect(second_item.position).to eq 2
expect(unrelated_item.position).to eq 1
expect(custom_item.position).to eq 7
end
end
end

View File

@@ -156,4 +156,36 @@ RSpec.describe AccountPolicy do
end
end
end
permissions :feature? do
context 'when account is featureable?' do
it 'permits' do
expect(subject).to permit(alice, john)
end
end
context 'when account is not featureable' do
before { allow(alice).to receive(:featureable?).and_return(false) }
it 'denies' do
expect(subject).to_not permit(john, alice)
end
end
context 'when account is blocked' do
before { alice.block!(john) }
it 'denies' do
expect(subject).to_not permit(alice, john)
end
end
context 'when account is blocking' do
before { john.block!(alice) }
it 'denies' do
expect(subject).to_not permit(alice, john)
end
end
end
end

View File

@@ -75,6 +75,33 @@ RSpec.describe 'Credentials' do
end
end
context 'with client credentials' do
let(:application) { Fabricate(:application, scopes: 'read admin:write') }
let(:token) { Fabricate(:client_credentials_token, application: application, scopes: 'read admin:write') }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
it 'returns http success and returns app information' do
subject
expect(response).to have_http_status(200)
expect(response.content_type)
.to start_with('application/json')
expect(response.parsed_body).to match(
a_hash_including(
id: token.application.id.to_s,
name: token.application.name,
website: token.application.website,
scopes: token.application.scopes.map(&:to_s),
redirect_uris: token.application.redirect_uris,
# Deprecated properties as of 4.3:
redirect_uri: token.application.redirect_uri.split.first,
vapid_key: Rails.configuration.x.vapid.public_key
)
)
end
end
context 'without an oauth token' do
let(:headers) { {} }

View File

@@ -0,0 +1,35 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe AddAccountToCollectionService do
subject { described_class.new }
let(:collection) { Fabricate.create(:collection) }
describe '#call' do
context 'when given a featurable account' do
let(:account) { Fabricate(:account) }
it 'creates a new CollectionItem in the `accepted` state' do
expect do
subject.call(collection, account)
end.to change(collection.collection_items, :count).by(1)
new_item = collection.collection_items.last
expect(new_item.state).to eq 'accepted'
expect(new_item.account).to eq account
end
end
context 'when given an account that is not featureable' do
let(:account) { Fabricate(:account, discoverable: false) }
it 'raises an error' do
expect do
subject.call(collection, account)
end.to raise_error(Mastodon::NotPermittedError)
end
end
end
end

View File

@@ -120,6 +120,8 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => {
manifest: true,
outDir,
assetsDir: 'assets',
assetsInlineLimit: (filePath, _) =>
/\.woff2?$/.exec(filePath) ? false : undefined,
rollupOptions: {
input: await findEntrypoints(),
output: {

View File

@@ -12,10 +12,10 @@ __metadata:
languageName: node
linkType: hard
"@acemir/cssom@npm:^0.9.23":
version: 0.9.24
resolution: "@acemir/cssom@npm:0.9.24"
checksum: 10c0/1c7bf8a61a74d9ecbc3b12fba697384461b3234441ed5a10f5c34aef91fdf4f1e3322fcd6659a8eaddd591eddc2259efd278212236100d90a6e16f77794d98bd
"@acemir/cssom@npm:^0.9.28":
version: 0.9.28
resolution: "@acemir/cssom@npm:0.9.28"
checksum: 10c0/1e192d216c4236171d9930b42b9a965052d4578b23c6ddaa17c7c3d0820ffb872258544a83af163ae2d41b3bdccd6b6c4c14b2d32eb9f8b8b63972d74f46bd83
languageName: node
linkType: hard
@@ -46,29 +46,29 @@ __metadata:
languageName: node
linkType: hard
"@asamuzakjp/css-color@npm:^4.0.3":
version: 4.0.4
resolution: "@asamuzakjp/css-color@npm:4.0.4"
"@asamuzakjp/css-color@npm:^4.1.0":
version: 4.1.0
resolution: "@asamuzakjp/css-color@npm:4.1.0"
dependencies:
"@csstools/css-calc": "npm:^2.1.4"
"@csstools/css-color-parser": "npm:^3.0.10"
"@csstools/css-color-parser": "npm:^3.1.0"
"@csstools/css-parser-algorithms": "npm:^3.0.5"
"@csstools/css-tokenizer": "npm:^3.0.4"
lru-cache: "npm:^11.1.0"
checksum: 10c0/5a4eb3c8594f58f3df06c867a6cda4a33f702f5cd682d6afa5074813f16fd05e732653ac79bd6fc66390554e158ac478103ad5e885fd9cf154b69bb67639e82f
lru-cache: "npm:^11.2.2"
checksum: 10c0/097b9270a5befb765885dda43d6914ccbaa575565525d307e8ba3ba07f98e466062f4a77b9657e65160db9110c41c59cf5a6937479647f73637aeddf5c421579
languageName: node
linkType: hard
"@asamuzakjp/dom-selector@npm:^6.7.4":
version: 6.7.5
resolution: "@asamuzakjp/dom-selector@npm:6.7.5"
"@asamuzakjp/dom-selector@npm:^6.7.6":
version: 6.7.6
resolution: "@asamuzakjp/dom-selector@npm:6.7.6"
dependencies:
"@asamuzakjp/nwsapi": "npm:^2.3.9"
bidi-js: "npm:^1.0.3"
css-tree: "npm:^3.1.0"
is-potential-custom-element-name: "npm:^1.0.1"
lru-cache: "npm:^11.2.2"
checksum: 10c0/72ac4dc45aac9165222345aacc1db51a84094f159e9e2fa71bc89befd2d78fd00a76c7ff9a8a1ceb60e7ce198a4ec0275a4d878bea67b756cadbf3d9680162c4
lru-cache: "npm:^11.2.4"
checksum: 10c0/1715faae0787f0c8430b3a0ff3db8576a5b9a4f964408d0808fc2060ab01e0c2f5d8e26409de54b8641433c891dab8b561b196e58798811146084c561a4954ce
languageName: node
linkType: hard
@@ -1275,7 +1275,7 @@ __metadata:
languageName: node
linkType: hard
"@csstools/css-color-parser@npm:^3.0.10, @csstools/css-color-parser@npm:^3.1.0":
"@csstools/css-color-parser@npm:^3.1.0":
version: 3.1.0
resolution: "@csstools/css-color-parser@npm:3.1.0"
dependencies:
@@ -1297,7 +1297,7 @@ __metadata:
languageName: node
linkType: hard
"@csstools/css-syntax-patches-for-csstree@npm:^1.0.14":
"@csstools/css-syntax-patches-for-csstree@npm:1.0.14":
version: 1.0.14
resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.0.14"
peerDependencies:
@@ -3341,10 +3341,10 @@ __metadata:
languageName: node
linkType: hard
"@rolldown/pluginutils@npm:1.0.0-beta.47":
version: 1.0.0-beta.47
resolution: "@rolldown/pluginutils@npm:1.0.0-beta.47"
checksum: 10c0/eb0cfa7334d66f090c47eaac612174936b05f26e789352428cb6e03575b590f355de30d26b42576ea4e613d8887b587119d19b2e4b3a8909ceb232ca1cf746c8
"@rolldown/pluginutils@npm:1.0.0-beta.53":
version: 1.0.0-beta.53
resolution: "@rolldown/pluginutils@npm:1.0.0-beta.53"
checksum: 10c0/e8b0a7eb76be22f6f103171f28072de821525a4e400454850516da91a7381957932ff0ce495f227bcb168e86815788b0c1d249ca9e34dca366a82c8825b714ce
languageName: node
linkType: hard
@@ -4830,18 +4830,18 @@ __metadata:
linkType: hard
"@vitejs/plugin-react@npm:^5.0.0":
version: 5.1.1
resolution: "@vitejs/plugin-react@npm:5.1.1"
version: 5.1.2
resolution: "@vitejs/plugin-react@npm:5.1.2"
dependencies:
"@babel/core": "npm:^7.28.5"
"@babel/plugin-transform-react-jsx-self": "npm:^7.27.1"
"@babel/plugin-transform-react-jsx-source": "npm:^7.27.1"
"@rolldown/pluginutils": "npm:1.0.0-beta.47"
"@rolldown/pluginutils": "npm:1.0.0-beta.53"
"@types/babel__core": "npm:^7.20.5"
react-refresh: "npm:^0.18.0"
peerDependencies:
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
checksum: 10c0/e590efaea1eabfbb1beb6e8c9fac0742fd299808e3368e63b2825ce24740adb8a28fcb2668b14b7ca1bdb42890cfefe94d02dd358dcbbf8a27ddf377b9a82abf
checksum: 10c0/d788f269cdf7474425071ba7c4ea7013f174ddaef12b758defe809a551a03ac62a4a80cd858872deb618e7936ccc7cffe178bc12b62e9c836a467e13f15b9390
languageName: node
linkType: hard
@@ -6253,14 +6253,14 @@ __metadata:
languageName: node
linkType: hard
"cssstyle@npm:^5.3.3":
version: 5.3.3
resolution: "cssstyle@npm:5.3.3"
"cssstyle@npm:^5.3.4":
version: 5.3.4
resolution: "cssstyle@npm:5.3.4"
dependencies:
"@asamuzakjp/css-color": "npm:^4.0.3"
"@csstools/css-syntax-patches-for-csstree": "npm:^1.0.14"
"@asamuzakjp/css-color": "npm:^4.1.0"
"@csstools/css-syntax-patches-for-csstree": "npm:1.0.14"
css-tree: "npm:^3.1.0"
checksum: 10c0/0e082992851a1ded3662bda420f86dc1c90510a21cf237ddf573a1e121a722a3f78bb8f6eb46b33f267da25162e8e1fe968f7002114c9ab1d0d4e11dad9c5ee8
checksum: 10c0/7499ea8cbc2f759ded275428e0811d147baa6a964a44577711cee5edabee2230cf76b6bd20a556603f99ebc6fff80afdcba6c00bcbb1d41ae50cd09cd9fe9a2d
languageName: node
linkType: hard
@@ -8959,12 +8959,12 @@ __metadata:
linkType: hard
"jsdom@npm:^27.0.0":
version: 27.2.0
resolution: "jsdom@npm:27.2.0"
version: 27.3.0
resolution: "jsdom@npm:27.3.0"
dependencies:
"@acemir/cssom": "npm:^0.9.23"
"@asamuzakjp/dom-selector": "npm:^6.7.4"
cssstyle: "npm:^5.3.3"
"@acemir/cssom": "npm:^0.9.28"
"@asamuzakjp/dom-selector": "npm:^6.7.6"
cssstyle: "npm:^5.3.4"
data-urls: "npm:^6.0.0"
decimal.js: "npm:^10.6.0"
html-encoding-sniffer: "npm:^4.0.0"
@@ -8987,7 +8987,7 @@ __metadata:
peerDependenciesMeta:
canvas:
optional: true
checksum: 10c0/52d847e1aef099071d66d1d9aedcdd2f15e7ea781da9cfb41dc0d4caf741c5870c346396f8d1182d611427ae47a53f69a6f16410c698950e5809d3fed5a1672d
checksum: 10c0/b022ed8f6ce175afd97fbd42eb65b03b2be3b23df86cf87f018b6d2e757682fe8348e719a14780d6fa3fe8a65e531ba71b38db80f312818a32b77f01e31f267e
languageName: node
linkType: hard
@@ -9394,10 +9394,10 @@ __metadata:
languageName: node
linkType: hard
"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.2":
version: 11.2.2
resolution: "lru-cache@npm:11.2.2"
checksum: 10c0/72d7831bbebc85e2bdefe01047ee5584db69d641c48d7a509e86f66f6ee111b30af7ec3bd68a967d47b69a4b1fa8bbf3872630bd06a63b6735e6f0a5f1c8e83d
"lru-cache@npm:^11.0.0, lru-cache@npm:^11.2.2, lru-cache@npm:^11.2.4":
version: 11.2.4
resolution: "lru-cache@npm:11.2.4"
checksum: 10c0/4a24f9b17537619f9144d7b8e42cd5a225efdfd7076ebe7b5e7dc02b860a818455201e67fbf000765233fe7e339d3c8229fc815e9b58ee6ede511e07608c19b2
languageName: node
linkType: hard
@@ -14077,8 +14077,8 @@ __metadata:
linkType: hard
"vite@npm:^6.0.0 || ^7.0.0, vite@npm:^7.1.1":
version: 7.2.6
resolution: "vite@npm:7.2.6"
version: 7.2.7
resolution: "vite@npm:7.2.7"
dependencies:
esbuild: "npm:^0.25.0"
fdir: "npm:^6.5.0"
@@ -14127,7 +14127,7 @@ __metadata:
optional: true
bin:
vite: bin/vite.js
checksum: 10c0/d444a159ab8f0f854d596d1938f201b449d59ed4d336e587be9dc89005467214d85848c212c2495f76a8421372ffe4d061d023d659600f1aaa3ba5ac13e804f7
checksum: 10c0/0c502d9eb898d9c05061dbd8fd199f280b524bbb4c12ab5f88c7b12779947386684a269e4dd0aa424aa35bcd857f1aa44aadb9ea764702a5043af433052455b5
languageName: node
linkType: hard