diff --git a/Gemfile b/Gemfile index e54842fcda..599686d5ba 100644 --- a/Gemfile +++ b/Gemfile @@ -24,7 +24,7 @@ gem 'ruby-vips', '~> 2.2', require: false gem 'active_model_serializers', '~> 0.10' gem 'addressable', '~> 2.8' -gem 'bootsnap', '~> 1.18.0', require: false +gem 'bootsnap', '~> 1.19.0', require: false gem 'browser' gem 'charlock_holmes', '~> 0.7.7' gem 'chewy', '~> 7.3' diff --git a/Gemfile.lock b/Gemfile.lock index 73c54fc633..4f24c4106d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -129,7 +129,7 @@ GEM binding_of_caller (1.0.1) debug_inspector (>= 1.2.0) blurhash (0.1.8) - bootsnap (1.18.6) + bootsnap (1.19.0) msgpack (~> 1.2) brakeman (7.1.1) racc @@ -349,7 +349,7 @@ GEM azure-blob (~> 0.5.2) hashie (~> 5.0) jmespath (1.6.2) - json (2.15.2) + json (2.16.0) json-canonicalization (1.0.0) json-jwt (1.17.0) activesupport (>= 4.2) @@ -446,7 +446,7 @@ GEM mime-types-data (3.2025.0924) mini_mime (1.1.5) mini_portile2 (2.8.9) - minitest (5.26.0) + minitest (5.26.1) msgpack (1.8.0) multi_json (1.17.0) mutex_m (0.3.0) @@ -759,7 +759,7 @@ GEM rubocop-ast (>= 1.47.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.47.1) + rubocop-ast (1.48.0) parser (>= 3.3.7.2) prism (~> 1.4) rubocop-capybara (2.22.1) @@ -778,10 +778,10 @@ GEM rack (>= 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.44.0, < 2.0) - rubocop-rspec (3.7.0) + rubocop-rspec (3.8.0) lint_roller (~> 1.1) - rubocop (~> 1.72, >= 1.72.1) - rubocop-rspec_rails (2.31.0) + rubocop (~> 1.81) + rubocop-rspec_rails (2.32.0) lint_roller (~> 1.1) rubocop (~> 1.72, >= 1.72.1) rubocop-rspec (~> 3.5) @@ -941,7 +941,7 @@ DEPENDENCIES better_errors (~> 2.9) binding_of_caller (~> 1.0) blurhash (~> 0.1) - bootsnap (~> 1.18.0) + bootsnap (~> 1.19.0) brakeman (~> 7.0) browser bundler-audit (~> 0.9) diff --git a/app/controllers/admin/site_uploads_controller.rb b/app/controllers/admin/site_uploads_controller.rb index 96e61cf6bb..e30c783a49 100644 --- a/app/controllers/admin/site_uploads_controller.rb +++ b/app/controllers/admin/site_uploads_controller.rb @@ -9,7 +9,7 @@ module Admin @site_upload.destroy! - redirect_back fallback_location: admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg') + redirect_back_or_to admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg') end private diff --git a/app/javascript/mastodon/components/hotkeys/index.tsx b/app/javascript/mastodon/components/hotkeys/index.tsx index b1484ec3ac..81ca28eb87 100644 --- a/app/javascript/mastodon/components/hotkeys/index.tsx +++ b/app/javascript/mastodon/components/hotkeys/index.tsx @@ -180,25 +180,24 @@ export function useHotkeys(handlers: HandlerMap) { if (shouldHandleEvent) { const matchCandidates: { - handler: (event: KeyboardEvent) => void; + // A candidate will be have an undefined handler if it's matched, + // but handled in a parent component rather than this one. + handler: ((event: KeyboardEvent) => void) | undefined; priority: number; }[] = []; (Object.keys(hotkeyMatcherMap) as HotkeyName[]).forEach( (handlerName) => { const handler = handlersRef.current[handlerName]; + const hotkeyMatcher = hotkeyMatcherMap[handlerName]; - if (handler) { - const hotkeyMatcher = hotkeyMatcherMap[handlerName]; + const { isMatch, priority } = hotkeyMatcher( + event, + bufferedKeys.current, + ); - const { isMatch, priority } = hotkeyMatcher( - event, - bufferedKeys.current, - ); - - if (isMatch) { - matchCandidates.push({ handler, priority }); - } + if (isMatch) { + matchCandidates.push({ handler, priority }); } }, ); diff --git a/app/javascript/mastodon/components/status/handled_link.tsx b/app/javascript/mastodon/components/status/handled_link.tsx index 43763d9c32..8b6db98fda 100644 --- a/app/javascript/mastodon/components/status/handled_link.tsx +++ b/app/javascript/mastodon/components/status/handled_link.tsx @@ -38,7 +38,7 @@ export const HandledLink: FC> = ({ return ( @@ -71,7 +71,7 @@ export const HandledLink: FC> = ({ return ( ({ quoteToPrivate: !!state.getIn(['compose', 'quoted_status_id']) && state.getIn(['compose', 'privacy']) === 'private' + && state.getIn(['statuses', state.getIn(['compose', 'quoted_status_id']), 'account']) !== me && !state.getIn(['settings', 'dismissed_banners', PRIVATE_QUOTE_MODAL_ID]), isInReply: state.getIn(['compose', 'in_reply_to']) !== null, lang: state.getIn(['compose', 'language']), diff --git a/app/javascript/mastodon/features/emoji/index.ts b/app/javascript/mastodon/features/emoji/index.ts index 11ee26aac2..4b0f79133c 100644 --- a/app/javascript/mastodon/features/emoji/index.ts +++ b/app/javascript/mastodon/features/emoji/index.ts @@ -1,6 +1,7 @@ import { initialState } from '@/mastodon/initial_state'; import { toSupportedLocale } from './locale'; +import type { LocaleOrCustom } from './types'; import { emojiLogger } from './utils'; // eslint-disable-next-line import/default -- Importing via worker loader. import EmojiWorker from './worker?worker&inline'; @@ -24,19 +25,17 @@ export function initializeEmoji() { } if (worker) { - // Assign worker to const to make TS happy inside the event listener. - const thisWorker = worker; const timeoutId = setTimeout(() => { log('worker is not ready after timeout'); worker = null; void fallbackLoad(); }, WORKER_TIMEOUT); - thisWorker.addEventListener('message', (event: MessageEvent) => { + worker.addEventListener('message', (event: MessageEvent) => { const { data: message } = event; if (message === 'ready') { log('worker ready, loading data'); clearTimeout(timeoutId); - thisWorker.postMessage('custom'); + messageWorker('custom'); void loadEmojiLocale(userLocale); // Load English locale as well, because people are still used to // using it from before we supported other locales. @@ -55,20 +54,35 @@ export function initializeEmoji() { async function fallbackLoad() { log('falling back to main thread for loading'); const { importCustomEmojiData } = await import('./loader'); - await importCustomEmojiData(); + const emojis = await importCustomEmojiData(); + if (emojis) { + log('loaded %d custom emojis', emojis.length); + } await loadEmojiLocale(userLocale); if (userLocale !== 'en') { await loadEmojiLocale('en'); } } -export async function loadEmojiLocale(localeString: string) { +async function loadEmojiLocale(localeString: string) { const locale = toSupportedLocale(localeString); + const { importEmojiData, localeToPath } = await import('./loader'); if (worker) { - worker.postMessage(locale); + const path = await localeToPath(locale); + log('asking worker to load locale %s from %s', locale, path); + messageWorker(locale, path); } else { - const { importEmojiData } = await import('./loader'); - await importEmojiData(locale); + const emojis = await importEmojiData(locale); + if (emojis) { + log('loaded %d emojis to locale %s', emojis.length, locale); + } } } + +function messageWorker(locale: LocaleOrCustom, path?: string) { + if (!worker) { + return; + } + worker.postMessage({ locale, path }); +} diff --git a/app/javascript/mastodon/features/emoji/loader.ts b/app/javascript/mastodon/features/emoji/loader.ts index 330c5e6a2a..7251559d6b 100644 --- a/app/javascript/mastodon/features/emoji/loader.ts +++ b/app/javascript/mastodon/features/emoji/loader.ts @@ -8,44 +8,64 @@ import { putLatestEtag, } from './database'; import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale'; -import type { CustomEmojiData, LocaleOrCustom } from './types'; -import { emojiLogger } from './utils'; +import type { CustomEmojiData } from './types'; -const log = emojiLogger('loader'); - -export async function importEmojiData(localeString: string) { +export async function importEmojiData(localeString: string, path?: string) { const locale = toSupportedLocale(localeString); - const emojis = await fetchAndCheckEtag(locale); + + // Validate the provided path. + if (path && !/^[/a-z]*\/packs\/assets\/compact-\w+\.json$/.test(path)) { + throw new Error('Invalid path for emoji data'); + } else { + // Otherwise get the path if not provided. + path ??= await localeToPath(locale); + } + + const emojis = await fetchAndCheckEtag(locale, path); if (!emojis) { return; } const flattenedEmojis: FlatCompactEmoji[] = flattenEmojiData(emojis); - log('loaded %d for %s locale', flattenedEmojis.length, locale); await putEmojiData(flattenedEmojis, locale); + return flattenedEmojis; } export async function importCustomEmojiData() { - const emojis = await fetchAndCheckEtag('custom'); + const emojis = await fetchAndCheckEtag( + 'custom', + '/api/v1/custom_emojis', + ); if (!emojis) { return; } - log('loaded %d custom emojis', emojis.length); await putCustomEmojiData(emojis); + return emojis; } -async function fetchAndCheckEtag( - localeOrCustom: LocaleOrCustom, +const modules = import.meta.glob( + '../../../../../node_modules/emojibase-data/**/compact.json', + { + query: '?url', + import: 'default', + }, +); + +export function localeToPath(locale: Locale) { + const key = `../../../../../node_modules/emojibase-data/${locale}/compact.json`; + if (!modules[key] || typeof modules[key] !== 'function') { + throw new Error(`Unsupported locale: ${locale}`); + } + return modules[key](); +} + +export async function fetchAndCheckEtag( + localeString: string, + path: string, ): Promise { - const locale = toSupportedLocaleOrCustom(localeOrCustom); + const locale = toSupportedLocaleOrCustom(localeString); // Use location.origin as this script may be loaded from a CDN domain. - const url = new URL(location.origin); - if (locale === 'custom') { - url.pathname = '/api/v1/custom_emojis'; - } else { - const modulePath = await localeToPath(locale); - url.pathname = modulePath; - } + const url = new URL(path, location.origin); const oldEtag = await loadLatestEtag(locale); const response = await fetch(url, { @@ -60,38 +80,20 @@ async function fetchAndCheckEtag( } if (!response.ok) { throw new Error( - `Failed to fetch emoji data for ${localeOrCustom}: ${response.statusText}`, + `Failed to fetch emoji data for ${locale}: ${response.statusText}`, ); } const data = (await response.json()) as ResultType; if (!Array.isArray(data)) { - throw new Error( - `Unexpected data format for ${localeOrCustom}: expected an array`, - ); + throw new Error(`Unexpected data format for ${locale}: expected an array`); } // Store the ETag for future requests const etag = response.headers.get('ETag'); if (etag) { - await putLatestEtag(etag, localeOrCustom); + await putLatestEtag(etag, localeString); } return data; } - -const modules = import.meta.glob( - '../../../../../node_modules/emojibase-data/**/compact.json', - { - query: '?url', - import: 'default', - }, -); - -function localeToPath(locale: Locale) { - const key = `../../../../../node_modules/emojibase-data/${locale}/compact.json`; - if (!modules[key] || typeof modules[key] !== 'function') { - throw new Error(`Unsupported locale: ${locale}`); - } - return modules[key](); -} diff --git a/app/javascript/mastodon/features/emoji/render.test.ts b/app/javascript/mastodon/features/emoji/render.test.ts index 05dbc388c4..3c96cbfb55 100644 --- a/app/javascript/mastodon/features/emoji/render.test.ts +++ b/app/javascript/mastodon/features/emoji/render.test.ts @@ -162,7 +162,7 @@ describe('loadEmojiDataToState', () => { const dbCall = vi .spyOn(db, 'loadEmojiByHexcode') .mockRejectedValue(new db.LocaleNotLoadedError('en')); - vi.spyOn(loader, 'importEmojiData').mockResolvedValueOnce(); + vi.spyOn(loader, 'importEmojiData').mockResolvedValueOnce(undefined); const consoleCall = vi .spyOn(console, 'warn') .mockImplementationOnce(() => null); diff --git a/app/javascript/mastodon/features/emoji/worker.ts b/app/javascript/mastodon/features/emoji/worker.ts index 6fb7d36e93..5360484d77 100644 --- a/app/javascript/mastodon/features/emoji/worker.ts +++ b/app/javascript/mastodon/features/emoji/worker.ts @@ -1,18 +1,25 @@ -import { importEmojiData, importCustomEmojiData } from './loader'; +import { importCustomEmojiData, importEmojiData } from './loader'; addEventListener('message', handleMessage); self.postMessage('ready'); // After the worker is ready, notify the main thread -function handleMessage(event: MessageEvent) { - const { data: locale } = event; - void loadData(locale); +function handleMessage(event: MessageEvent<{ locale: string; path?: string }>) { + const { + data: { locale, path }, + } = event; + void loadData(locale, path); } -async function loadData(locale: string) { - if (locale !== 'custom') { - await importEmojiData(locale); +async function loadData(locale: string, path?: string) { + let importCount: number | undefined; + if (locale === 'custom') { + importCount = (await importCustomEmojiData())?.length; + } else if (path) { + importCount = (await importEmojiData(locale, path))?.length; } else { - await importCustomEmojiData(); + throw new Error('Path is required for loading locale emoji data'); + } + if (importCount) { + self.postMessage(`loaded ${importCount} emojis into ${locale}`); } - self.postMessage(`loaded ${locale}`); } diff --git a/app/javascript/mastodon/features/status/components/detailed_status.tsx b/app/javascript/mastodon/features/status/components/detailed_status.tsx index d326734a01..b0c66b9338 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.tsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.tsx @@ -4,7 +4,7 @@ @typescript-eslint/no-unsafe-assignment */ import type { CSSProperties } from 'react'; -import { useState, useRef, useCallback } from 'react'; +import { useState, useRef, useCallback, useEffect } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -55,6 +55,8 @@ export const DetailedStatus: React.FC<{ pictureInPicture: any; onToggleHidden?: (status: any) => void; onToggleMediaVisibility?: () => void; + ancestors?: number; + multiColumn?: boolean; }> = ({ status, onOpenMedia, @@ -69,6 +71,8 @@ export const DetailedStatus: React.FC<{ pictureInPicture, onToggleMediaVisibility, onToggleHidden, + ancestors = 0, + multiColumn = false, }) => { const properStatus = status?.get('reblog') ?? status; const [height, setHeight] = useState(0); @@ -123,6 +127,30 @@ export const DetailedStatus: React.FC<{ if (onTranslate) onTranslate(status); }, [onTranslate, status]); + // The component is managed and will change if the status changes + // Ancestors can increase when loading a thread, in which case we want to scroll, + // or decrease if a post is deleted, in which case we don't want to mess with it + const previousAncestors = useRef(-1); + useEffect(() => { + if (nodeRef.current && previousAncestors.current < ancestors) { + nodeRef.current.scrollIntoView(true); + + // In the single-column interface, `scrollIntoView` will put the post behind the header, so compensate for that. + if (!multiColumn) { + const offset = document + .querySelector('.column-header__wrapper') + ?.getBoundingClientRect().bottom; + + if (offset) { + const scrollingElement = document.scrollingElement ?? document.body; + scrollingElement.scrollBy(0, -offset); + } + } + } + + previousAncestors.current = ancestors; + }, [ancestors, multiColumn]); + if (!properStatus) { return null; } diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index f3ce9858ba..77e8af4b67 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -164,8 +164,6 @@ class Status extends ImmutablePureComponent { componentDidMount () { attachFullscreenListener(this.onFullScreenChange); - - this._scrollStatusIntoView(); } UNSAFE_componentWillReceiveProps (nextProps) { @@ -487,35 +485,11 @@ class Status extends ImmutablePureComponent { this.statusNode = c; }; - _scrollStatusIntoView () { - const { status, multiColumn } = this.props; - - if (status) { - requestIdleCallback(() => { - this.statusNode?.scrollIntoView(true); - - // In the single-column interface, `scrollIntoView` will put the post behind the header, - // so compensate for that. - if (!multiColumn) { - const offset = document.querySelector('.column-header__wrapper')?.getBoundingClientRect()?.bottom; - if (offset) { - const scrollingElement = document.scrollingElement || document.body; - scrollingElement.scrollBy(0, -offset); - } - } - }); - } - } - componentDidUpdate (prevProps) { - const { status, ancestorsIds, descendantsIds } = this.props; + const { status, descendantsIds } = this.props; const isSameStatus = status && (prevProps.status?.get('id') === status.get('id')); - if (status && (ancestorsIds.length > prevProps.ancestorsIds.length || !isSameStatus)) { - this._scrollStatusIntoView(); - } - // Only highlight replies after the initial load if (prevProps.descendantsIds.length && isSameStatus) { const newRepliesIds = difference(descendantsIds, prevProps.descendantsIds); @@ -619,6 +593,8 @@ class Status extends ImmutablePureComponent { showMedia={this.state.showMedia} onToggleMediaVisibility={this.handleToggleMediaVisibility} pictureInPicture={pictureInPicture} + ancestors={this.props.ancestorsIds.length} + multiColumn={multiColumn} /> Praeferentias > Regulas scriptionis praedefinitas.", + "visibility_modal.privacy_label": "Visibilitas", + "visibility_modal.quote_followers": "Sectatores tantum", + "visibility_modal.quote_label": "Quis citare potest", + "visibility_modal.quote_nobody": "Sicut me", + "visibility_modal.quote_public": "quisquis", + "visibility_modal.save": "Servare" } diff --git a/app/javascript/mastodon/main.tsx b/app/javascript/mastodon/main.tsx index f89baf66cd..249baf65fc 100644 --- a/app/javascript/mastodon/main.tsx +++ b/app/javascript/mastodon/main.tsx @@ -9,7 +9,6 @@ import { me, reduceMotion } from 'mastodon/initial_state'; import ready from 'mastodon/ready'; import { store } from 'mastodon/store'; -import { initializeEmoji } from './features/emoji'; import { isProduction, isDevelopment } from './utils/environment'; function main() { @@ -30,6 +29,7 @@ function main() { }); } + const { initializeEmoji } = await import('./features/emoji/index'); initializeEmoji(); const root = createRoot(mountNode); diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index d4ed1bd505..784050d942 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -341,8 +341,8 @@ export const composeReducer = (state = initialState, action) => { const isDirect = state.get('privacy') === 'direct'; return state .set('quoted_status_id', isDirect ? null : status.get('id')) - .set('spoiler', status.get('sensitive')) - .set('spoiler_text', status.get('spoiler_text')) + .update('spoiler', spoiler => (spoiler) || !!status.get('spoiler_text')) + .update('spoiler_text', (spoiler_text) => spoiler_text || status.get('spoiler_text')) .update('privacy', (visibility) => { if (['public', 'unlisted'].includes(visibility) && status.get('visibility') === 'private') { return 'private'; diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 925f2aa4de..777128bf6f 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -327,9 +327,9 @@ $content-width: 840px; font-weight: 700; color: $primary-text-color; text-transform: none; - padding-bottom: 0; + padding-top: 0; margin-bottom: 0; - border-bottom: 0; + border-top: 0; .comment { display: block; diff --git a/app/javascript/styles_new/mastodon/admin.scss b/app/javascript/styles_new/mastodon/admin.scss index ee0cec1a1e..d0cd3c5137 100644 --- a/app/javascript/styles_new/mastodon/admin.scss +++ b/app/javascript/styles_new/mastodon/admin.scss @@ -319,9 +319,9 @@ $content-width: 840px; font-weight: 700; color: var(--color-text-primary); text-transform: none; - padding-bottom: 0; + padding-top: 0; margin-bottom: 0; - border-bottom: 0; + border-top: 0; .comment { display: block; diff --git a/app/models/ip_block.rb b/app/models/ip_block.rb index 4c95ac38de..b3b678a6a1 100644 --- a/app/models/ip_block.rb +++ b/app/models/ip_block.rb @@ -31,9 +31,10 @@ class IpBlock < ApplicationRecord after_commit :reset_cache - def to_log_human_identifier + def to_cidr "#{ip}/#{ip.prefix}" end + alias to_log_human_identifier to_cidr class << self def blocked?(remote_ip) diff --git a/app/serializers/rest/admin/ip_block_serializer.rb b/app/serializers/rest/admin/ip_block_serializer.rb index 6a38f8b566..4b782da53a 100644 --- a/app/serializers/rest/admin/ip_block_serializer.rb +++ b/app/serializers/rest/admin/ip_block_serializer.rb @@ -9,6 +9,6 @@ class REST::Admin::IpBlockSerializer < ActiveModel::Serializer end def ip - "#{object.ip}/#{object.ip.prefix}" + object.to_cidr end end diff --git a/app/views/admin/ip_blocks/_ip_block.html.haml b/app/views/admin/ip_blocks/_ip_block.html.haml index 3dc6f8f8e5..0a1b864c6e 100644 --- a/app/views/admin/ip_blocks/_ip_block.html.haml +++ b/app/views/admin/ip_blocks/_ip_block.html.haml @@ -3,7 +3,7 @@ = f.check_box :ip_block_ids, { multiple: true, include_hidden: false }, ip_block.id .batch-table__row__content.pending-account .pending-account__header - %samp= link_to "#{ip_block.ip}/#{ip_block.ip.prefix}", admin_accounts_path(ip: "#{ip_block.ip}/#{ip_block.ip.prefix}") + %samp= link_to ip_block.to_cidr, admin_accounts_path(ip: ip_block.to_cidr) - if ip_block.comment.present? · = ip_block.comment diff --git a/config/locales/da.yml b/config/locales/da.yml index 8369b90008..e8053aaf63 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -477,7 +477,7 @@ da: no_file: Ingen fil valgt export_domain_blocks: import: - description_html: En liste over domæneblokeringer er ved at blive importeret. Gennemgå listen meget nøje, især hvis man ikke selv har oprettet den. + description_html: Du er ved at importere en liste over domæneblokeringer. Gennemgå denne liste meget omhyggeligt, især hvis du ikke selv har udarbejdet den. existing_relationships_warning: Eksisterende følge-relationer private_comment_description_html: 'For at man lettere kan holde styr på, hvorfra importerede blokeringer kommer, oprettes disse med flg. private kommentar: %{comment}' private_comment_template: Importeret fra %{source} d. %{date} @@ -1186,7 +1186,7 @@ da: new_trending_statuses: title: Indlæg, der trender new_trending_tags: - title: Hashtags, der trender + title: Populære hashtags subject: Nye tendenser klar til gennemgang på %{instance} aliases: add_new: Opret alias diff --git a/config/locales/de.yml b/config/locales/de.yml index 402f421ed6..b085fc4f01 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1089,16 +1089,16 @@ de: tag_servers_measure: Server tag_uses_measure: insgesamt description_html: Diese Hashtags werden derzeit in vielen Beiträgen verwendet, die dein Server sieht. Dies kann deinen Nutzer*innen helfen, herauszufinden, worüber die Leute im Moment am meisten schreiben. Hashtags werden erst dann öffentlich angezeigt, wenn du sie genehmigst. - listable: Kann vorgeschlagen werden + listable: Darf empfohlen werden no_tag_selected: Keine Hashtags wurden geändert, da keine ausgewählt wurden - not_listable: Wird nicht vorgeschlagen - not_trendable: Wird in den Trends nicht angezeigt - not_usable: Kann nicht verwendet werden + not_listable: Darf nicht vorgeschlagen werden + not_trendable: In Trends nicht erlaubt + not_usable: In Beiträgen nicht erlaubt peaked_on_and_decaying: In den Trends am %{date}, jetzt absteigend title: Angesagte Hashtags - trendable: Darf in den Trends erscheinen + trendable: In Trends erlaubt trending_rank: Platz %{rank} - usable: Darf verwendet werden + usable: In Beiträgen erlaubt usage_comparison: Heute %{today}-mal und gestern %{yesterday}-mal verwendet used_by_over_week: one: In den vergangenen 7 Tagen von einem Profil verwendet @@ -1122,7 +1122,7 @@ de: title: Neue Regel für Profilnamen erstellen no_username_block_selected: Keine Regeln für Profilnamen wurden geändert, weil keine ausgewählt wurde(n) not_permitted: Nicht gestattet - title: Regeln für Profilnamen + title: Profilnamen updated_msg: Regel für Profilnamen erfolgreich aktualisiert warning_presets: add_new: Neu hinzufügen @@ -1285,7 +1285,7 @@ de: new_confirmation_instructions_sent: In wenigen Minuten wirst du eine neue E-Mail mit dem Bestätigungslink erhalten! title: Überprüfe dein E-Mail-Postfach sign_in: - preamble_html: Melde dich mit deinen Zugangsdaten für %{domain} an. Falls dein Konto auf einem anderen Server erstellt wurde, ist eine Anmeldung hier nicht möglich. + preamble_html: Melde dich mit deinen Zugangsdaten für %{domain} an. Falls dein Konto auf einem anderen Mastodon-Server erstellt wurde, ist eine Anmeldung hier nicht möglich. title: Bei %{domain} anmelden sign_up: manual_review: Registrierungen für den Server %{domain} werden manuell durch unsere Moderator*innen überprüft. Um uns dabei zu unterstützen, schreibe etwas über dich und sage uns, weshalb du ein Konto auf %{domain} anlegen möchtest. diff --git a/config/locales/eu.yml b/config/locales/eu.yml index 3da7a1194d..f73a1284d8 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -563,6 +563,7 @@ eu: create: Gehitu Moderazio Oharra created_msg: Instantziako moderazio oharra ongi sortu da! description_html: Ikusi eta idatzi oharrak beste moderatzaileentzat eta zuretzat etorkizunerako + title: Moderazio oharrak private_comment: Iruzkin pribatua public_comment: Iruzkin publikoa purge: Ezabatu betiko @@ -771,6 +772,8 @@ eu: description_html: Gehienek erabilera baldintzak irakurri eta onartu dituztela baieztatzen badute ere, orokorrean arazoren bat dagoen arte ez dituzte irakurtzen. Zerbitzariaren arauak begirada batean ikustea errazteko buletadun zerrenda batean bildu. Saiatu arauak labur eta sinple idazten, baina elementu askotan banatu gabe. edit: Editatu araua empty: Ez da zerbitzariko araurik definitu oraindik. + move_down: Behera mugitu + move_up: Mugitu gora title: Zerbitzariaren arauak translation: Itzulpena translations: Itzulpenak @@ -808,6 +811,14 @@ eu: all: Guztiei disabled: Inori ez users: Saioa hasita duten erabiltzaile lokalei + feed_access: + modes: + public: Edonork + landing_page: + values: + about: Honi buruz + local_feed: Jario lokala + trends: Joerak registrations: moderation_recommandation: Mesedez, ziurtatu moderazio-talde egokia eta erreaktiboa duzula erregistroak guztiei ireki aurretik! preamble: Kontrolatu nork sortu dezakeen kontua zerbitzarian. @@ -862,6 +873,7 @@ eu: original_status: Jatorrizko bidalketa quotes: Aipuak reblogs: Bultzadak + replied_to_html: "%{acct_link}(r)i erantzuten" status_changed: Bidalketa aldatuta status_title: "%{name} erabiltzailearen bidalketa" trending: Joera diff --git a/config/locales/hu.yml b/config/locales/hu.yml index c1d20eadeb..a957165f26 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -1672,6 +1672,7 @@ hu: disabled_account: A jelenlegi fiókod nem lesz teljesen használható ezután. Viszont elérhető lesz majd az adatexport funkció, valamint a reaktiválás is. followers: Ez a művelet az összes követődet a jelenlegi fiókról az újra fogja költöztetni only_redirect_html: Az is lehetséges, hogy csak átirányítást raksz a profilodra. + other_data: Semmilyen más adat (beleértve a bejegyzéseket és a követett fiókokat) nem lesz automatikusan áthelyezve redirect: A jelenlegi fiókod profiljára átirányításról szóló figyelmeztetést rakunk, valamint már nem fogjuk mutatni a keresésekben moderation: title: Moderáció @@ -1928,6 +1929,7 @@ hu: errors: in_reply_not_found: Már nem létezik az a bejegyzés, melyre válaszolni szeretnél. quoted_status_not_found: Már nem létezik az a bejegyzés, amelyből idézni szeretnél. + quoted_user_not_mentioned: Nem idézhet meg nem említett felhasználót egy privát említési bejegyzésben. over_character_limit: túllépted a maximális %{max} karakteres keretet pin_errors: direct: A csak a megemlített felhasználók számára látható bejegyzések nem tűzhetők ki diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 13ad3fe40e..fa81b360fa 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -928,6 +928,7 @@ ru: no_status_selected: Ничего не изменилось, так как ни один пост не был выделен open: Открыть запись original_status: Оригинальный пост + quotes: Цитаты reblogs: Продвинули replied_to_html: Ответ пользователю %{acct_link} status_changed: Пост изменен @@ -935,6 +936,7 @@ ru: title: Посты пользователя - @%{name} trending: Популярное view_publicly: Открыть по публичной ссылке + view_quoted_post: Просмотр цитируемого сообщения visibility: Видимость with_media: С файлами strikes: diff --git a/config/locales/simple_form.cs.yml b/config/locales/simple_form.cs.yml index b4f8ba94f7..ff3f56a0d4 100644 --- a/config/locales/simple_form.cs.yml +++ b/config/locales/simple_form.cs.yml @@ -374,7 +374,9 @@ cs: jurisdiction: Právní příslušnost min_age: Věková hranice user: + date_of_birth_1i: Rok date_of_birth_2i: Měsíc + date_of_birth_3i: Den role: Role time_zone: Časové pásmo user_role: diff --git a/config/locales/simple_form.cy.yml b/config/locales/simple_form.cy.yml index 9d1abbf0a6..5a723d20f7 100644 --- a/config/locales/simple_form.cy.yml +++ b/config/locales/simple_form.cy.yml @@ -376,7 +376,9 @@ cy: jurisdiction: Awdurdodaeth gyfreithiol min_age: Isafswm oedran user: + date_of_birth_1i: Blwyddyn date_of_birth_2i: Mis + date_of_birth_3i: Diwrnod role: Rôl time_zone: Cylchfa amser user_role: diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index 46b61919a5..392d7076d1 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -353,10 +353,10 @@ de: indexable: Profilseite in Suchmaschinen einbeziehen show_application: App anzeigen, über die ich einen Beitrag veröffentlicht habe tag: - listable: Erlaube, dass dieser Hashtag in Suchen und Empfehlungen erscheint + listable: Dieser Hashtag darf in Suchen und Empfehlungen erscheinen name: Hashtag - trendable: Erlaube, dass dieser Hashtag in den Trends erscheint - usable: Beiträge dürfen diesen Hashtag lokal verwenden + trendable: Dieser Hashtag darf in den Trends erscheinen + usable: Dieser Hashtag darf lokal in Beiträgen verwendet werden terms_of_service: changelog: Was hat sich geändert? effective_date: Datum des Inkrafttretens diff --git a/config/locales/simple_form.hu.yml b/config/locales/simple_form.hu.yml index 6468298c72..aa118bf5d6 100644 --- a/config/locales/simple_form.hu.yml +++ b/config/locales/simple_form.hu.yml @@ -88,6 +88,7 @@ hu: activity_api_enabled: Helyi bejegyzések, aktív felhasználók és új regisztrációk száma heti bontásban app_icon: WEBP, PNG, GIF vagy JPG. Mobileszközökön az alkalmazás alapértelmezett ikonját felülírja egy egyéni ikonnal. backups_retention_period: A felhasználók archívumokat állíthatnak elő a bejegyzéseikből, hogy később letöltsék azokat. Ha pozitív értékre van állítva, akkor a megadott számú nap után automatikusan törölve lesznek a tárhelyedről. + bootstrap_timeline_accounts: Ezek a fiókok rögzítve lesznek az új felhasználók követési ajánlásai tetején. Add meg a fiókok vesszővel elválasztott listáját. closed_registrations_message: Akkor jelenik meg, amikor a regisztráció le van zárva content_cache_retention_period: Minden más kiszolgálóról származó bejegyzés (megtolásokkal és válaszokkal együtt) törölve lesz a megadott számú nap elteltével, függetlenül a helyi felhasználók ezekkel a bejegyzésekkel történő interakcióitól. Ebben azok a bejegyzések is benne vannak, melyeket a helyi felhasználó könyvjelzőzött vagy kedvencnek jelölt. A különböző kiszolgálók felhasználói közötti privát üzenetek is el fognak veszni visszaállíthatatlanul. Ennek a beállításnak a használata különleges felhasználási esetekre javasolt, mert számos felhasználói elvárás fog eltörni, ha általános céllal használják. custom_css: A Mastodon webes verziójában használhatsz egyéni stílusokat. @@ -371,7 +372,9 @@ hu: jurisdiction: Joghatóság min_age: Minimális életkor user: + date_of_birth_1i: Év date_of_birth_2i: Hónap + date_of_birth_3i: Nap role: Szerep time_zone: Időzóna user_role: diff --git a/config/vite/plugin-mastodon-themes.ts b/config/vite/plugin-mastodon-themes.ts index 3e7bc7a8a8..98bda4cab0 100644 --- a/config/vite/plugin-mastodon-themes.ts +++ b/config/vite/plugin-mastodon-themes.ts @@ -40,13 +40,15 @@ export function MastodonThemes(): Plugin { // Get all files mentioned in the themes.yml file. const themes = await loadThemesFromConfig(projectRoot); + const allThemes = { + ...themes, + default_theme_tokens: 'styles_new/application.scss', + 'mastodon-light_theme_tokens': 'styles_new/mastodon-light.scss', + contrast_theme_tokens: 'styles_new/contrast.scss', + }; - for (const [themeName, themePath] of Object.entries(themes)) { + for (const [themeName, themePath] of Object.entries(allThemes)) { entrypoints[`themes/${themeName}`] = path.resolve(jsRoot, themePath); - entrypoints[`themes/${themeName}_theme_tokens`] = path.resolve( - jsRoot, - themePath.replace('styles/', 'styles_new/'), - ); } return { diff --git a/lib/mastodon/cli/ip_blocks.rb b/lib/mastodon/cli/ip_blocks.rb index ea6b508c6f..ba31e6b5ce 100644 --- a/lib/mastodon/cli/ip_blocks.rb +++ b/lib/mastodon/cli/ip_blocks.rb @@ -107,9 +107,9 @@ module Mastodon::CLI IpBlock.severity_no_access.find_each do |ip_block| case options[:format] when 'nginx' - say "deny #{ip_block.ip}/#{ip_block.ip.prefix};" + say "deny #{ip_block.to_cidr};" else - say "#{ip_block.ip}/#{ip_block.ip.prefix}" + say ip_block.to_cidr end end end diff --git a/lib/mastodon/cli/upgrade.rb b/lib/mastodon/cli/upgrade.rb index 2cb5105794..d5822cacc0 100644 --- a/lib/mastodon/cli/upgrade.rb +++ b/lib/mastodon/cli/upgrade.rb @@ -123,12 +123,12 @@ module Mastodon::CLI progress.log("Moving #{previous_path} to #{upgraded_path}") if options[:verbose] begin - move_previous_to_upgraded + move_previous_to_upgraded(previous_path, upgraded_path) rescue => e progress.log(pastel.red("Error processing #{previous_path}: #{e}")) success = false - remove_directory + remove_directory(upgraded_path) end end diff --git a/spec/lib/mastodon/cli/ip_blocks_spec.rb b/spec/lib/mastodon/cli/ip_blocks_spec.rb index d531b8b7a8..1f903ac065 100644 --- a/spec/lib/mastodon/cli/ip_blocks_spec.rb +++ b/spec/lib/mastodon/cli/ip_blocks_spec.rb @@ -251,12 +251,12 @@ RSpec.describe Mastodon::CLI::IpBlocks do it 'exports blocked IPs with "no_access" severity in plain format' do expect { subject } - .to output_results("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}") + .to output_results("#{first_ip_range_block.to_cidr}\n#{second_ip_range_block.to_cidr}") end it 'does not export blocked IPs with different severities' do expect { subject } - .to_not output_results("#{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}") + .to_not output_results(third_ip_range_block.to_cidr) end end @@ -265,19 +265,19 @@ RSpec.describe Mastodon::CLI::IpBlocks do it 'exports blocked IPs with "no_access" severity in plain format' do expect { subject } - .to output_results("deny #{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};\ndeny #{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix};") + .to output_results("deny #{first_ip_range_block.to_cidr};\ndeny #{second_ip_range_block.to_cidr};") end it 'does not export blocked IPs with different severities' do expect { subject } - .to_not output_results("deny #{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};") + .to_not output_results("deny #{third_ip_range_block.to_cidr};") end end context 'when --format option is not provided' do it 'exports blocked IPs in plain format by default' do expect { subject } - .to output_results("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}") + .to output_results("#{first_ip_range_block.to_cidr}\n#{second_ip_range_block.to_cidr}") end end end diff --git a/spec/lib/mastodon/redis_configuration_spec.rb b/spec/lib/mastodon/redis_configuration_spec.rb index d8a8dd86d2..560b16ed30 100644 --- a/spec/lib/mastodon/redis_configuration_spec.rb +++ b/spec/lib/mastodon/redis_configuration_spec.rb @@ -73,8 +73,6 @@ RSpec.describe Mastodon::RedisConfiguration do end shared_examples 'sentinel support' do |prefix = nil| - prefix = prefix ? "#{prefix}_" : '' - context 'when configuring sentinel support' do around do |example| ClimateControl.modify "#{prefix}REDIS_PASSWORD": 'testpass1', "#{prefix}REDIS_HOST": 'redis2.example.com', "#{prefix}REDIS_SENTINELS": '192.168.0.1:3000,192.168.0.2:4000', "#{prefix}REDIS_SENTINEL_MASTER": 'mainsentinel' do @@ -199,7 +197,7 @@ RSpec.describe Mastodon::RedisConfiguration do it_behaves_like 'secondary configuration', 'SIDEKIQ' it_behaves_like 'setting a different driver' - it_behaves_like 'sentinel support', 'SIDEKIQ' + it_behaves_like 'sentinel support', 'SIDEKIQ_' end describe '#cache' do @@ -225,6 +223,6 @@ RSpec.describe Mastodon::RedisConfiguration do it_behaves_like 'secondary configuration', 'CACHE' it_behaves_like 'setting a different driver' - it_behaves_like 'sentinel support', 'CACHE' + it_behaves_like 'sentinel support', 'CACHE_' end end diff --git a/spec/models/ip_block_spec.rb b/spec/models/ip_block_spec.rb index 18fb7ea136..a8b0809511 100644 --- a/spec/models/ip_block_spec.rb +++ b/spec/models/ip_block_spec.rb @@ -26,6 +26,22 @@ RSpec.describe IpBlock do end end + describe '#to_cidr' do + subject { Fabricate.build(:ip_block, ip:).to_cidr } + + context 'with an IP and a specified prefix' do + let(:ip) { '192.168.1.0/24' } + + it { is_expected.to eq('192.168.1.0/24') } + end + + context 'with an IP and a default prefix' do + let(:ip) { '192.168.1.0' } + + it { is_expected.to eq('192.168.1.0/32') } + end + end + describe '.blocked?' do context 'when the IP is blocked' do it 'returns true' do diff --git a/spec/requests/api/v1/admin/ip_blocks_spec.rb b/spec/requests/api/v1/admin/ip_blocks_spec.rb index 59ef8d2966..161781c8ed 100644 --- a/spec/requests/api/v1/admin/ip_blocks_spec.rb +++ b/spec/requests/api/v1/admin/ip_blocks_spec.rb @@ -97,7 +97,7 @@ RSpec.describe 'IP Blocks' do expect(response.parsed_body) .to include( - ip: eq("#{ip_block.ip}/#{ip_block.ip.prefix}"), + ip: eq(ip_block.to_cidr), severity: eq(ip_block.severity.to_s) ) end @@ -216,7 +216,7 @@ RSpec.describe 'IP Blocks' do expect(response.content_type) .to start_with('application/json') expect(response.parsed_body).to match(hash_including({ - ip: "#{ip_block.ip}/#{ip_block.ip.prefix}", + ip: ip_block.to_cidr, severity: 'sign_up_requires_approval', comment: 'Decreasing severity', })) diff --git a/spec/requests/api/v1/statuses/translations_spec.rb b/spec/requests/api/v1/statuses/translations_spec.rb index e95ec1b101..eb17d9f6a7 100644 --- a/spec/requests/api/v1/statuses/translations_spec.rb +++ b/spec/requests/api/v1/statuses/translations_spec.rb @@ -50,6 +50,21 @@ RSpec.describe 'API V1 Statuses Translations' do end end + context 'with a public status marked with the same language as the current locale when translation backend cannot do same-language translation' do + let(:status) { Fabricate(:status, account: user.account, text: 'Esto está en español pero está marcado como inglés.', language: 'en') } + + it 'returns http forbidden with error message' do + subject + + expect(response) + .to have_http_status(403) + expect(response.media_type) + .to eq('application/json') + expect(response.parsed_body) + .to include(error: /not allowed/) + end + end + context 'with a private status' do let(:status) { Fabricate(:status, visibility: :private, account: user.account, text: 'Hola', language: 'es') } diff --git a/yarn.lock b/yarn.lock index abc39e42e9..134b5e1bc6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -97,39 +97,39 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.18.9, @babel/core@npm:^7.21.3, @babel/core@npm:^7.24.4, @babel/core@npm:^7.26.10, @babel/core@npm:^7.28.0, @babel/core@npm:^7.28.4": - version: 7.28.4 - resolution: "@babel/core@npm:7.28.4" +"@babel/core@npm:^7.18.9, @babel/core@npm:^7.21.3, @babel/core@npm:^7.24.4, @babel/core@npm:^7.26.10, @babel/core@npm:^7.28.0, @babel/core@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/core@npm:7.28.5" dependencies: "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.3" + "@babel/generator": "npm:^7.28.5" "@babel/helper-compilation-targets": "npm:^7.27.2" "@babel/helper-module-transforms": "npm:^7.28.3" "@babel/helpers": "npm:^7.28.4" - "@babel/parser": "npm:^7.28.4" + "@babel/parser": "npm:^7.28.5" "@babel/template": "npm:^7.27.2" - "@babel/traverse": "npm:^7.28.4" - "@babel/types": "npm:^7.28.4" + "@babel/traverse": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" "@jridgewell/remapping": "npm:^2.3.5" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10c0/ef5a6c3c6bf40d3589b5593f8118cfe2602ce737412629fb6e26d595be2fcbaae0807b43027a5c42ec4fba5b895ff65891f2503b5918c8a3ea3542ab44d4c278 + checksum: 10c0/535f82238027621da6bdffbdbe896ebad3558b311d6f8abc680637a9859b96edbf929ab010757055381570b29cf66c4a295b5618318d27a4273c0e2033925e72 languageName: node linkType: hard -"@babel/generator@npm:^7.28.3": - version: 7.28.3 - resolution: "@babel/generator@npm:7.28.3" +"@babel/generator@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/generator@npm:7.28.5" dependencies: - "@babel/parser": "npm:^7.28.3" - "@babel/types": "npm:^7.28.2" + "@babel/parser": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" "@jridgewell/gen-mapping": "npm:^0.3.12" "@jridgewell/trace-mapping": "npm:^0.3.28" jsesc: "npm:^3.0.2" - checksum: 10c0/0ff58bcf04f8803dcc29479b547b43b9b0b828ec1ee0668e92d79f9e90f388c28589056637c5ff2fd7bcf8d153c990d29c448d449d852bf9d1bc64753ca462bc + checksum: 10c0/9f219fe1d5431b6919f1a5c60db8d5d34fe546c0d8f5a8511b32f847569234ffc8032beb9e7404649a143f54e15224ecb53a3d11b6bb85c3203e573d91fca752 languageName: node linkType: hard @@ -334,7 +334,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.3, @babel/parser@npm:^7.28.4": +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.5": version: 7.28.5 resolution: "@babel/parser@npm:7.28.5" dependencies: @@ -1194,22 +1194,22 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.26.10, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4": - version: 7.28.4 - resolution: "@babel/traverse@npm:7.28.4" +"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.26.10, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/traverse@npm:7.28.5" dependencies: "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.3" + "@babel/generator": "npm:^7.28.5" "@babel/helper-globals": "npm:^7.28.0" - "@babel/parser": "npm:^7.28.4" + "@babel/parser": "npm:^7.28.5" "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.28.4" + "@babel/types": "npm:^7.28.5" debug: "npm:^4.3.1" - checksum: 10c0/ee678fdd49c9f54a32e07e8455242390d43ce44887cea6567b233fe13907b89240c377e7633478a32c6cf1be0e17c2f7f3b0c59f0666e39c5074cc47b968489c + checksum: 10c0/f6c4a595993ae2b73f2d4cd9c062f2e232174d293edd4abe1d715bd6281da8d99e47c65857e8d0917d9384c65972f4acdebc6749a7c40a8fcc38b3c7fb3e706f languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.25.4, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5, @babel/types@npm:^7.4.4": +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.25.4, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5, @babel/types@npm:^7.4.4": version: 7.28.5 resolution: "@babel/types@npm:7.28.5" dependencies: @@ -3317,10 +3317,10 @@ __metadata: languageName: node linkType: hard -"@rolldown/pluginutils@npm:1.0.0-beta.43": - version: 1.0.0-beta.43 - resolution: "@rolldown/pluginutils@npm:1.0.0-beta.43" - checksum: 10c0/1c17a0b16c277a0fdbab080fd22ef91e37c1f0d710ecfdacb6a080068062eb14ff030d0e9d2ec2325a1d4246dba0c49625755c82c0090f6cbf98d16e80183e02 +"@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 languageName: node linkType: hard @@ -4815,18 +4815,18 @@ __metadata: linkType: hard "@vitejs/plugin-react@npm:^5.0.0": - version: 5.1.0 - resolution: "@vitejs/plugin-react@npm:5.1.0" + version: 5.1.1 + resolution: "@vitejs/plugin-react@npm:5.1.1" dependencies: - "@babel/core": "npm:^7.28.4" + "@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.43" + "@rolldown/pluginutils": "npm:1.0.0-beta.47" "@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/e192a12e2b854df109eafb1d06c0bc848e8e2b162c686aa6b999b1048658983e72674b2068ccc37562fcce44d32ad92b65f3a4e1897a0cb7859c2ee69cc63eac + checksum: 10c0/e590efaea1eabfbb1beb6e8c9fac0742fd299808e3368e63b2825ce24740adb8a28fcb2668b14b7ca1bdb42890cfefe94d02dd358dcbbf8a27ddf377b9a82abf languageName: node linkType: hard