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

Merge upstream changes up to 562ea656f495f0619e393b7d93bd07c5abd28e5a
This commit is contained in:
Claire 2026-01-22 12:51:22 +01:00 committed by GitHub
commit 1239c8d480
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 604 additions and 221 deletions

View File

@ -50,9 +50,19 @@ const preview: Preview = {
dynamicTitle: true, dynamicTitle: true,
}, },
}, },
theme: {
description: 'Theme for the story',
toolbar: {
title: 'Theme',
icon: 'circlehollow',
items: [{ value: 'light' }, { value: 'dark' }],
dynamicTitle: true,
},
},
}, },
initialGlobals: { initialGlobals: {
locale: 'en', locale: 'en',
theme: 'light',
}, },
decorators: [ decorators: [
(Story, { parameters, globals, args, argTypes }) => { (Story, { parameters, globals, args, argTypes }) => {
@ -135,6 +145,13 @@ const preview: Preview = {
</IntlProvider> </IntlProvider>
); );
}, },
(Story, { globals }) => {
const theme = (globals.theme as string) || 'light';
useEffect(() => {
document.body.setAttribute('data-color-scheme', theme);
}, [theme]);
return <Story />;
},
(Story) => ( (Story) => (
<MemoryRouter> <MemoryRouter>
<Story /> <Story />

View File

@ -74,6 +74,7 @@ class Api::V1Alpha::CollectionsController < Api::BaseController
.order(created_at: :desc) .order(created_at: :desc)
.offset(offset_param) .offset(offset_param)
.limit(limit_param(DEFAULT_COLLECTIONS_LIMIT)) .limit(limit_param(DEFAULT_COLLECTIONS_LIMIT))
@collections = @collections.discoverable unless @account == current_account
end end
def set_collection def set_collection

View File

@ -1,31 +0,0 @@
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import GroupsIcon from '@/material-icons/400-24px/group.svg?react';
import PersonIcon from '@/material-icons/400-24px/person.svg?react';
import SmartToyIcon from '@/material-icons/400-24px/smart_toy.svg?react';
export const Badge = ({ icon = <PersonIcon />, label, domain, roleId }) => (
<div className='account-role' data-account-role-id={roleId}>
{icon}
{label}
{domain && <span className='account-role__domain'>{domain}</span>}
</div>
);
Badge.propTypes = {
icon: PropTypes.node,
label: PropTypes.node,
domain: PropTypes.node,
roleId: PropTypes.string
};
export const GroupBadge = () => (
<Badge icon={<GroupsIcon />} label={<FormattedMessage id='account.badges.group' defaultMessage='Group' />} />
);
export const AutomatedBadge = () => (
<Badge icon={<SmartToyIcon />} label={<FormattedMessage id='account.badges.bot' defaultMessage='Automated' />} />
);

View File

@ -0,0 +1,46 @@
import type { FC, ReactNode } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import GroupsIcon from '@/material-icons/400-24px/group.svg?react';
import PersonIcon from '@/material-icons/400-24px/person.svg?react';
import SmartToyIcon from '@/material-icons/400-24px/smart_toy.svg?react';
export const Badge: FC<{
label: ReactNode;
icon?: ReactNode;
className?: string;
domain?: ReactNode;
roleId?: string;
}> = ({ icon = <PersonIcon />, label, className, domain, roleId }) => (
<div
className={classNames('account-role', className)}
data-account-role-id={roleId}
>
{icon}
{label}
{domain && <span className='account-role__domain'>{domain}</span>}
</div>
);
export const GroupBadge: FC<{ className?: string }> = ({ className }) => (
<Badge
icon={<GroupsIcon />}
label={
<FormattedMessage id='account.badges.group' defaultMessage='Group' />
}
className={className}
/>
);
export const AutomatedBadge: FC<{ className?: string }> = ({ className }) => (
<Badge
icon={<SmartToyIcon />}
label={
<FormattedMessage id='account.badges.bot' defaultMessage='Automated' />
}
className={className}
/>
);

View File

@ -10,7 +10,9 @@ import type { MiniCardProps } from '.';
import classes from './styles.module.css'; import classes from './styles.module.css';
interface MiniCardListProps { interface MiniCardListProps {
cards?: (Pick<MiniCardProps, 'label' | 'value'> & { key?: Key })[]; cards?: (Pick<MiniCardProps, 'label' | 'value' | 'className'> & {
key?: Key;
})[];
className?: string; className?: string;
onOverflowClick?: MouseEventHandler; onOverflowClick?: MouseEventHandler;
} }
@ -42,6 +44,7 @@ export const MiniCardList: FC<MiniCardListProps> = ({
label={card.label} label={card.label}
value={card.value} value={card.value}
hidden={hasOverflow && index >= hiddenIndex} hidden={hasOverflow && index >= hiddenIndex}
className={card.className}
/> />
))} ))}
</dl> </dl>

View File

@ -1,12 +1,19 @@
import type { FC } from 'react'; import type { FC, ReactNode } from 'react';
import { import {
AutomatedBadge, AutomatedBadge,
Badge, Badge,
GroupBadge, GroupBadge,
} from '@/flavours/glitch/components/badge'; } from '@/flavours/glitch/components/badge';
import { Icon } from '@/flavours/glitch/components/icon';
import { useAccount } from '@/flavours/glitch/hooks/useAccount'; import { useAccount } from '@/flavours/glitch/hooks/useAccount';
import type { AccountRole } from '@/flavours/glitch/models/account';
import { useAppSelector } from '@/flavours/glitch/store'; import { useAppSelector } from '@/flavours/glitch/store';
import IconAdmin from '@/images/icons/icon_admin.svg?react';
import { isRedesignEnabled } from '../common';
import classes from './redesign.module.scss';
export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => { export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
const account = useAccount(accountId); const account = useAccount(accountId);
@ -19,22 +26,32 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
return null; return null;
} }
const className = isRedesignEnabled() ? classes.badge : '';
if (account.bot) { if (account.bot) {
badges.push(<AutomatedBadge key='bot-badge' />); badges.push(<AutomatedBadge key='bot-badge' className={className} />);
} else if (account.group) { } else if (account.group) {
badges.push(<GroupBadge key='group-badge' />); badges.push(<GroupBadge key='group-badge' className={className} />);
} }
const domain = account.acct.includes('@') const domain = account.acct.includes('@')
? account.acct.split('@')[1] ? account.acct.split('@')[1]
: localDomain; : localDomain;
account.roles.forEach((role) => { account.roles.forEach((role) => {
let icon: ReactNode = undefined;
if (isAdminBadge(role)) {
icon = (
<Icon icon={IconAdmin} id='badge-admin' className={classes.badgeIcon} />
);
}
badges.push( badges.push(
<Badge <Badge
key={`role-badge-${role.get('id')}`} key={role.id}
label={<span>{role.get('name')}</span>} label={role.name}
domain={domain} className={className}
roleId={role.get('id')} domain={isRedesignEnabled() ? `(${domain})` : domain}
roleId={role.id}
icon={icon}
/>, />,
); );
}); });
@ -43,5 +60,10 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
return null; return null;
} }
return <div className='account__header__badges'>{badges}</div>; return <div className={'account__header__badges'}>{badges}</div>;
}; };
function isAdminBadge(role: AccountRole) {
const name = role.name.toLowerCase();
return isRedesignEnabled() && (name === 'admin' || name === 'owner');
}

View File

@ -3,15 +3,19 @@ import type { FC } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { openModal } from '@/flavours/glitch/actions/modal'; import { openModal } from '@/flavours/glitch/actions/modal';
import { AccountFields } from '@/flavours/glitch/components/account_fields'; import { AccountFields } from '@/flavours/glitch/components/account_fields';
import { EmojiHTML } from '@/flavours/glitch/components/emoji/html'; import { EmojiHTML } from '@/flavours/glitch/components/emoji/html';
import { FormattedDateWrapper } from '@/flavours/glitch/components/formatted_date'; import { FormattedDateWrapper } from '@/flavours/glitch/components/formatted_date';
import { Icon } from '@/flavours/glitch/components/icon';
import { MiniCardList } from '@/flavours/glitch/components/mini_card/list'; import { MiniCardList } from '@/flavours/glitch/components/mini_card/list';
import { useElementHandledLink } from '@/flavours/glitch/components/status/handled_link'; import { useElementHandledLink } from '@/flavours/glitch/components/status/handled_link';
import { useAccount } from '@/flavours/glitch/hooks/useAccount'; import { useAccount } from '@/flavours/glitch/hooks/useAccount';
import type { Account } from '@/flavours/glitch/models/account'; import type { Account } from '@/flavours/glitch/models/account';
import { useAppDispatch } from '@/flavours/glitch/store'; import { useAppDispatch } from '@/flavours/glitch/store';
import IconVerified from '@/images/icons/icon_verified.svg?react';
import { isRedesignEnabled } from '../common'; import { isRedesignEnabled } from '../common';
@ -55,25 +59,40 @@ const RedesignAccountHeaderFields: FC<{ account: Account }> = ({ account }) => {
const htmlHandlers = useElementHandledLink(); const htmlHandlers = useElementHandledLink();
const cards = useMemo( const cards = useMemo(
() => () =>
account.fields.toArray().map(({ value_emojified, name_emojified }) => ({ account.fields
label: ( .toArray()
<EmojiHTML .map(({ value_emojified, name_emojified, verified_at }) => ({
htmlString={name_emojified} label: (
extraEmojis={account.emojis} <>
className='translate' <EmojiHTML
as='span' htmlString={name_emojified}
{...htmlHandlers} extraEmojis={account.emojis}
/> className='translate'
), as='span'
value: ( {...htmlHandlers}
<EmojiHTML />
as='span' {!!verified_at && (
htmlString={value_emojified} <Icon
extraEmojis={account.emojis} id='verified'
{...htmlHandlers} icon={IconVerified}
/> className={classes.fieldIconVerified}
), />
})), )}
</>
),
value: (
<EmojiHTML
as='span'
htmlString={value_emojified}
extraEmojis={account.emojis}
{...htmlHandlers}
/>
),
className: classNames(
classes.fieldCard,
!!verified_at && classes.fieldCardVerified,
),
})),
[account.emojis, account.fields, htmlHandlers], [account.emojis, account.fields, htmlHandlers],
); );

View File

@ -5,10 +5,12 @@ import { FormattedMessage, useIntl } from 'react-intl';
import { DisplayName } from '@/flavours/glitch/components/display_name'; import { DisplayName } from '@/flavours/glitch/components/display_name';
import { AnimateEmojiProvider } from '@/flavours/glitch/components/emoji/context'; import { AnimateEmojiProvider } from '@/flavours/glitch/components/emoji/context';
import { EmojiHTML } from '@/flavours/glitch/components/emoji/html'; import { EmojiHTML } from '@/flavours/glitch/components/emoji/html';
import { Icon } from '@/flavours/glitch/components/icon';
import { IconButton } from '@/flavours/glitch/components/icon_button'; import { IconButton } from '@/flavours/glitch/components/icon_button';
import { LoadingIndicator } from '@/flavours/glitch/components/loading_indicator'; import { LoadingIndicator } from '@/flavours/glitch/components/loading_indicator';
import { useElementHandledLink } from '@/flavours/glitch/components/status/handled_link'; import { useElementHandledLink } from '@/flavours/glitch/components/status/handled_link';
import { useAccount } from '@/flavours/glitch/hooks/useAccount'; import { useAccount } from '@/flavours/glitch/hooks/useAccount';
import IconVerified from '@/images/icons/icon_verified.svg?react';
import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import classes from './redesign.module.scss'; import classes from './redesign.module.scss';
@ -56,7 +58,10 @@ export const AccountFieldsModal: FC<{
<AnimateEmojiProvider> <AnimateEmojiProvider>
<dl className={classes.modalFieldsList}> <dl className={classes.modalFieldsList}>
{account.fields.map((field, index) => ( {account.fields.map((field, index) => (
<div key={index} className={classes.modalFieldItem}> <div
key={index}
className={`${classes.modalFieldItem} ${classes.fieldCard}`}
>
<EmojiHTML <EmojiHTML
as='dt' as='dt'
htmlString={field.name_emojified} htmlString={field.name_emojified}
@ -64,12 +69,21 @@ export const AccountFieldsModal: FC<{
className='translate' className='translate'
{...htmlHandlers} {...htmlHandlers}
/> />
<EmojiHTML <dd>
as='dd' <EmojiHTML
htmlString={field.value_emojified} as='span'
extraEmojis={account.emojis} htmlString={field.value_emojified}
{...htmlHandlers} extraEmojis={account.emojis}
/> {...htmlHandlers}
/>
{!!field.verified_at && (
<Icon
id='verified'
icon={IconVerified}
className={classes.fieldIconVerified}
/>
)}
</dd>
</div> </div>
))} ))}
</dl> </dl>

View File

@ -39,10 +39,64 @@ h1.name > small {
} }
} }
.badge {
background-color: var(--color-bg-secondary);
border: none;
color: var(--color-text-secondary);
font-weight: 600;
> span {
font-weight: unset;
opacity: 1;
}
}
svg.badgeIcon {
opacity: 1;
fill: revert-layer;
path {
fill: revert-layer;
}
}
.fieldList { .fieldList {
margin-top: 16px; margin-top: 16px;
} }
.fieldCard {
position: relative;
a {
color: var(--color-text-brand);
text-decoration: none;
}
}
.fieldCardVerified {
background-color: var(--color-bg-brand-softer);
dt {
padding-right: 1rem;
}
.fieldIconVerified {
position: absolute;
top: 4px;
right: 4px;
}
}
.fieldIconVerified {
width: 1rem;
height: 1rem;
// Need to override .icon path.
path {
fill: revert-layer;
}
}
.fieldNumbersWrapper { .fieldNumbersWrapper {
a { a {
font-weight: unset; font-weight: unset;
@ -85,4 +139,9 @@ h1.name > small {
font-weight: 600; font-weight: 600;
font-size: 15px; font-size: 15px;
} }
.fieldIconVerified {
vertical-align: middle;
margin-left: 4px;
}
} }

View File

@ -142,6 +142,7 @@
var(--border-strength-primary) var(--border-strength-primary)
)}; )};
--color-border-media: rgb(252 248 255 / 15%); --color-border-media: rgb(252 248 255 / 15%);
--color-border-verified: rgb(220, 3, 240);
--color-border-on-bg-secondary: #{utils.css-alpha( --color-border-on-bg-secondary: #{utils.css-alpha(
var(--color-indigo-200), var(--color-indigo-200),
calc(var(--border-strength-primary) / 1.5) calc(var(--border-strength-primary) / 1.5)

View File

@ -140,6 +140,7 @@
var(--color-grey-950) var(--border-strength-primary) var(--color-grey-950) var(--border-strength-primary)
); );
--color-border-media: rgb(252 248 255 / 15%); --color-border-media: rgb(252 248 255 / 15%);
--color-border-verified: rgb(220, 3, 240);
--color-border-on-bg-secondary: var(--color-grey-200); --color-border-on-bg-secondary: var(--color-grey-200);
--color-border-on-bg-brand-softer: var(--color-indigo-200); --color-border-on-bg-brand-softer: var(--color-indigo-200);
--color-border-on-bg-error-softer: #{utils.css-alpha( --color-border-on-bg-error-softer: #{utils.css-alpha(

View File

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path fill="url(#badgeGradient)" d="M8 .666c.301 0 .595.094.839.268l.101.079.006.005c1.092.956 2.603 1.649 3.721 1.649A1.333 1.333 0 0 1 14 4v4.667c0 1.842-.652 3.259-1.702 4.336-1.032 1.058-2.417 1.76-3.852 2.26l-.005.002a1.334 1.334 0 0 1-.879-.01c-1.437-.496-2.825-1.195-3.858-2.253C2.654 11.926 2 10.509 2 8.667V4a1.334 1.334 0 0 1 1.334-1.333c1.118 0 2.634-.7 3.72-1.65l.007-.004C7.322.789 7.656.666 8 .666Z"/>
<path stroke="#fff" fill="none" stroke-linecap="round" stroke-linejoin="round" d="M5 10.998A3.053 3.053 0 0 1 6.173 9.57a3.333 3.333 0 0 1 1.828-.54m0 0c.653 0 1.29.189 1.826.54.537.353.946.852 1.173 1.43M8 9.03c1.179 0 2.133-.903 2.133-2.015C10.133 5.902 9.178 5 8 5c-1.179 0-2.134.902-2.134 2.015 0 1.112.956 2.015 2.135 2.015Z"/>
<defs>
<linearGradient id="badgeGradient" x1=".5" x2="14" y1="2.5" y2="15" gradientUnits="userSpaceOnUse">
<stop stop-color="#E17100"/>
<stop offset="1" stop-color="#F0B100"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16">
<path fill="url(#VerifiedGradient)" d="M8 .837a3.168 3.168 0 0 1 2.47 1.187 3.166 3.166 0 0 1 2.601.906 3.168 3.168 0 0 1 .905 2.6A3.167 3.167 0 0 1 15.164 8a3.172 3.172 0 0 1-1.188 2.47 3.167 3.167 0 0 1-.903 2.597 3.168 3.168 0 0 1-2.596.909 3.167 3.167 0 0 1-4.95.001 3.166 3.166 0 0 1-3.397-2.258 3.169 3.169 0 0 1-.107-1.24A3.168 3.168 0 0 1 .826 8a3.17 3.17 0 0 1 1.197-2.479 3.168 3.168 0 0 1 .91-2.593 3.166 3.166 0 0 1 2.596-.905A3.169 3.169 0 0 1 8 .837Z"/>
<path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.333" d="m6 8 1.333 1.333L10 6.667"/>
<defs>
<linearGradient id="VerifiedGradient" x1="-.966" x2="12.162" y1="2.629" y2="17.493" gradientUnits="userSpaceOnUse">
<stop offset=".13" stop-color="#5638CC"/>
<stop offset=".995" stop-color="#DC03F0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 930 B

View File

@ -1,31 +0,0 @@
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import GroupsIcon from '@/material-icons/400-24px/group.svg?react';
import PersonIcon from '@/material-icons/400-24px/person.svg?react';
import SmartToyIcon from '@/material-icons/400-24px/smart_toy.svg?react';
export const Badge = ({ icon = <PersonIcon />, label, domain, roleId }) => (
<div className='account-role' data-account-role-id={roleId}>
{icon}
{label}
{domain && <span className='account-role__domain'>{domain}</span>}
</div>
);
Badge.propTypes = {
icon: PropTypes.node,
label: PropTypes.node,
domain: PropTypes.node,
roleId: PropTypes.string
};
export const GroupBadge = () => (
<Badge icon={<GroupsIcon />} label={<FormattedMessage id='account.badges.group' defaultMessage='Group' />} />
);
export const AutomatedBadge = () => (
<Badge icon={<SmartToyIcon />} label={<FormattedMessage id='account.badges.bot' defaultMessage='Automated' />} />
);

View File

@ -0,0 +1,46 @@
import type { FC, ReactNode } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import GroupsIcon from '@/material-icons/400-24px/group.svg?react';
import PersonIcon from '@/material-icons/400-24px/person.svg?react';
import SmartToyIcon from '@/material-icons/400-24px/smart_toy.svg?react';
export const Badge: FC<{
label: ReactNode;
icon?: ReactNode;
className?: string;
domain?: ReactNode;
roleId?: string;
}> = ({ icon = <PersonIcon />, label, className, domain, roleId }) => (
<div
className={classNames('account-role', className)}
data-account-role-id={roleId}
>
{icon}
{label}
{domain && <span className='account-role__domain'>{domain}</span>}
</div>
);
export const GroupBadge: FC<{ className?: string }> = ({ className }) => (
<Badge
icon={<GroupsIcon />}
label={
<FormattedMessage id='account.badges.group' defaultMessage='Group' />
}
className={className}
/>
);
export const AutomatedBadge: FC<{ className?: string }> = ({ className }) => (
<Badge
icon={<SmartToyIcon />}
label={
<FormattedMessage id='account.badges.bot' defaultMessage='Automated' />
}
className={className}
/>
);

View File

@ -10,7 +10,9 @@ import type { MiniCardProps } from '.';
import classes from './styles.module.css'; import classes from './styles.module.css';
interface MiniCardListProps { interface MiniCardListProps {
cards?: (Pick<MiniCardProps, 'label' | 'value'> & { key?: Key })[]; cards?: (Pick<MiniCardProps, 'label' | 'value' | 'className'> & {
key?: Key;
})[];
className?: string; className?: string;
onOverflowClick?: MouseEventHandler; onOverflowClick?: MouseEventHandler;
} }
@ -42,6 +44,7 @@ export const MiniCardList: FC<MiniCardListProps> = ({
label={card.label} label={card.label}
value={card.value} value={card.value}
hidden={hasOverflow && index >= hiddenIndex} hidden={hasOverflow && index >= hiddenIndex}
className={card.className}
/> />
))} ))}
</dl> </dl>

View File

@ -1,9 +1,16 @@
import type { FC } from 'react'; import type { FC, ReactNode } from 'react';
import IconAdmin from '@/images/icons/icon_admin.svg?react';
import { AutomatedBadge, Badge, GroupBadge } from '@/mastodon/components/badge'; import { AutomatedBadge, Badge, GroupBadge } from '@/mastodon/components/badge';
import { Icon } from '@/mastodon/components/icon';
import { useAccount } from '@/mastodon/hooks/useAccount'; import { useAccount } from '@/mastodon/hooks/useAccount';
import type { AccountRole } from '@/mastodon/models/account';
import { useAppSelector } from '@/mastodon/store'; import { useAppSelector } from '@/mastodon/store';
import { isRedesignEnabled } from '../common';
import classes from './redesign.module.scss';
export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => { export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
const account = useAccount(accountId); const account = useAccount(accountId);
const localDomain = useAppSelector( const localDomain = useAppSelector(
@ -15,22 +22,32 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
return null; return null;
} }
const className = isRedesignEnabled() ? classes.badge : '';
if (account.bot) { if (account.bot) {
badges.push(<AutomatedBadge key='bot-badge' />); badges.push(<AutomatedBadge key='bot-badge' className={className} />);
} else if (account.group) { } else if (account.group) {
badges.push(<GroupBadge key='group-badge' />); badges.push(<GroupBadge key='group-badge' className={className} />);
} }
const domain = account.acct.includes('@') const domain = account.acct.includes('@')
? account.acct.split('@')[1] ? account.acct.split('@')[1]
: localDomain; : localDomain;
account.roles.forEach((role) => { account.roles.forEach((role) => {
let icon: ReactNode = undefined;
if (isAdminBadge(role)) {
icon = (
<Icon icon={IconAdmin} id='badge-admin' className={classes.badgeIcon} />
);
}
badges.push( badges.push(
<Badge <Badge
key={`role-badge-${role.get('id')}`} key={role.id}
label={<span>{role.get('name')}</span>} label={role.name}
domain={domain} className={className}
roleId={role.get('id')} domain={isRedesignEnabled() ? `(${domain})` : domain}
roleId={role.id}
icon={icon}
/>, />,
); );
}); });
@ -39,5 +56,10 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => {
return null; return null;
} }
return <div className='account__header__badges'>{badges}</div>; return <div className={'account__header__badges'}>{badges}</div>;
}; };
function isAdminBadge(role: AccountRole) {
const name = role.name.toLowerCase();
return isRedesignEnabled() && (name === 'admin' || name === 'owner');
}

View File

@ -3,10 +3,14 @@ import type { FC } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import IconVerified from '@/images/icons/icon_verified.svg?react';
import { openModal } from '@/mastodon/actions/modal'; import { openModal } from '@/mastodon/actions/modal';
import { AccountFields } from '@/mastodon/components/account_fields'; import { AccountFields } from '@/mastodon/components/account_fields';
import { EmojiHTML } from '@/mastodon/components/emoji/html'; import { EmojiHTML } from '@/mastodon/components/emoji/html';
import { FormattedDateWrapper } from '@/mastodon/components/formatted_date'; import { FormattedDateWrapper } from '@/mastodon/components/formatted_date';
import { Icon } from '@/mastodon/components/icon';
import { MiniCardList } from '@/mastodon/components/mini_card/list'; import { MiniCardList } from '@/mastodon/components/mini_card/list';
import { useElementHandledLink } from '@/mastodon/components/status/handled_link'; import { useElementHandledLink } from '@/mastodon/components/status/handled_link';
import { useAccount } from '@/mastodon/hooks/useAccount'; import { useAccount } from '@/mastodon/hooks/useAccount';
@ -55,25 +59,40 @@ const RedesignAccountHeaderFields: FC<{ account: Account }> = ({ account }) => {
const htmlHandlers = useElementHandledLink(); const htmlHandlers = useElementHandledLink();
const cards = useMemo( const cards = useMemo(
() => () =>
account.fields.toArray().map(({ value_emojified, name_emojified }) => ({ account.fields
label: ( .toArray()
<EmojiHTML .map(({ value_emojified, name_emojified, verified_at }) => ({
htmlString={name_emojified} label: (
extraEmojis={account.emojis} <>
className='translate' <EmojiHTML
as='span' htmlString={name_emojified}
{...htmlHandlers} extraEmojis={account.emojis}
/> className='translate'
), as='span'
value: ( {...htmlHandlers}
<EmojiHTML />
as='span' {!!verified_at && (
htmlString={value_emojified} <Icon
extraEmojis={account.emojis} id='verified'
{...htmlHandlers} icon={IconVerified}
/> className={classes.fieldIconVerified}
), />
})), )}
</>
),
value: (
<EmojiHTML
as='span'
htmlString={value_emojified}
extraEmojis={account.emojis}
{...htmlHandlers}
/>
),
className: classNames(
classes.fieldCard,
!!verified_at && classes.fieldCardVerified,
),
})),
[account.emojis, account.fields, htmlHandlers], [account.emojis, account.fields, htmlHandlers],
); );

View File

@ -2,9 +2,11 @@ import type { FC } from 'react';
import { FormattedMessage, useIntl } from 'react-intl'; import { FormattedMessage, useIntl } from 'react-intl';
import IconVerified from '@/images/icons/icon_verified.svg?react';
import { DisplayName } from '@/mastodon/components/display_name'; import { DisplayName } from '@/mastodon/components/display_name';
import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context'; import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context';
import { EmojiHTML } from '@/mastodon/components/emoji/html'; import { EmojiHTML } from '@/mastodon/components/emoji/html';
import { Icon } from '@/mastodon/components/icon';
import { IconButton } from '@/mastodon/components/icon_button'; import { IconButton } from '@/mastodon/components/icon_button';
import { LoadingIndicator } from '@/mastodon/components/loading_indicator'; import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
import { useElementHandledLink } from '@/mastodon/components/status/handled_link'; import { useElementHandledLink } from '@/mastodon/components/status/handled_link';
@ -56,7 +58,10 @@ export const AccountFieldsModal: FC<{
<AnimateEmojiProvider> <AnimateEmojiProvider>
<dl className={classes.modalFieldsList}> <dl className={classes.modalFieldsList}>
{account.fields.map((field, index) => ( {account.fields.map((field, index) => (
<div key={index} className={classes.modalFieldItem}> <div
key={index}
className={`${classes.modalFieldItem} ${classes.fieldCard}`}
>
<EmojiHTML <EmojiHTML
as='dt' as='dt'
htmlString={field.name_emojified} htmlString={field.name_emojified}
@ -64,12 +69,21 @@ export const AccountFieldsModal: FC<{
className='translate' className='translate'
{...htmlHandlers} {...htmlHandlers}
/> />
<EmojiHTML <dd>
as='dd' <EmojiHTML
htmlString={field.value_emojified} as='span'
extraEmojis={account.emojis} htmlString={field.value_emojified}
{...htmlHandlers} extraEmojis={account.emojis}
/> {...htmlHandlers}
/>
{!!field.verified_at && (
<Icon
id='verified'
icon={IconVerified}
className={classes.fieldIconVerified}
/>
)}
</dd>
</div> </div>
))} ))}
</dl> </dl>

View File

@ -39,10 +39,64 @@ h1.name > small {
} }
} }
.badge {
background-color: var(--color-bg-secondary);
border: none;
color: var(--color-text-secondary);
font-weight: 600;
> span {
font-weight: unset;
opacity: 1;
}
}
svg.badgeIcon {
opacity: 1;
fill: revert-layer;
path {
fill: revert-layer;
}
}
.fieldList { .fieldList {
margin-top: 16px; margin-top: 16px;
} }
.fieldCard {
position: relative;
a {
color: var(--color-text-brand);
text-decoration: none;
}
}
.fieldCardVerified {
background-color: var(--color-bg-brand-softer);
dt {
padding-right: 1rem;
}
.fieldIconVerified {
position: absolute;
top: 4px;
right: 4px;
}
}
.fieldIconVerified {
width: 1rem;
height: 1rem;
// Need to override .icon path.
path {
fill: revert-layer;
}
}
.fieldNumbersWrapper { .fieldNumbersWrapper {
a { a {
font-weight: unset; font-weight: unset;
@ -85,4 +139,9 @@ h1.name > small {
font-weight: 600; font-weight: 600;
font-size: 15px; font-size: 15px;
} }
.fieldIconVerified {
vertical-align: middle;
margin-left: 4px;
}
} }

View File

@ -57,6 +57,7 @@
"account.go_to_profile": "Ir al perfil", "account.go_to_profile": "Ir al perfil",
"account.hide_reblogs": "Ocultar impulsos de @{name}", "account.hide_reblogs": "Ocultar impulsos de @{name}",
"account.in_memoriam": "Cuenta conmemorativa.", "account.in_memoriam": "Cuenta conmemorativa.",
"account.joined_long": "Se unió el {date}",
"account.joined_short": "Se unió", "account.joined_short": "Se unió",
"account.languages": "Cambiar idiomas suscritos", "account.languages": "Cambiar idiomas suscritos",
"account.link_verified_on": "La propiedad de este enlace fue verificada el {date}", "account.link_verified_on": "La propiedad de este enlace fue verificada el {date}",
@ -90,6 +91,8 @@
"account.unmute": "Dejar de silenciar a @{name}", "account.unmute": "Dejar de silenciar a @{name}",
"account.unmute_notifications_short": "Dejar de silenciar notificaciones", "account.unmute_notifications_short": "Dejar de silenciar notificaciones",
"account.unmute_short": "Dejar de silenciar", "account.unmute_short": "Dejar de silenciar",
"account_fields_modal.close": "Cerrar",
"account_fields_modal.title": "Información de {name}",
"account_note.placeholder": "Haz clic para añadir nota", "account_note.placeholder": "Haz clic para añadir nota",
"admin.dashboard.daily_retention": "Tasa de retención de usuarios por día después del registro", "admin.dashboard.daily_retention": "Tasa de retención de usuarios por día después del registro",
"admin.dashboard.monthly_retention": "Tasa de retención de usuarios por mes después del registro", "admin.dashboard.monthly_retention": "Tasa de retención de usuarios por mes después del registro",
@ -589,6 +592,7 @@
"load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}", "load_pending": "{count, plural, one {# nuevo elemento} other {# nuevos elementos}}",
"loading_indicator.label": "Cargando…", "loading_indicator.label": "Cargando…",
"media_gallery.hide": "Ocultar", "media_gallery.hide": "Ocultar",
"minicard.more_items": "+{count}",
"moved_to_account_banner.text": "Tu cuenta {disabledAccount} está actualmente deshabilitada porque te has mudado a {movedToAccount}.", "moved_to_account_banner.text": "Tu cuenta {disabledAccount} está actualmente deshabilitada porque te has mudado a {movedToAccount}.",
"mute_modal.hide_from_notifications": "Ocultar de las notificaciones", "mute_modal.hide_from_notifications": "Ocultar de las notificaciones",
"mute_modal.hide_options": "Ocultar opciones", "mute_modal.hide_options": "Ocultar opciones",

View File

@ -142,6 +142,7 @@
var(--border-strength-primary) var(--border-strength-primary)
)}; )};
--color-border-media: rgb(252 248 255 / 15%); --color-border-media: rgb(252 248 255 / 15%);
--color-border-verified: rgb(220, 3, 240);
--color-border-on-bg-secondary: #{utils.css-alpha( --color-border-on-bg-secondary: #{utils.css-alpha(
var(--color-indigo-200), var(--color-indigo-200),
calc(var(--border-strength-primary) / 1.5) calc(var(--border-strength-primary) / 1.5)

View File

@ -140,6 +140,7 @@
var(--color-grey-950) var(--border-strength-primary) var(--color-grey-950) var(--border-strength-primary)
); );
--color-border-media: rgb(252 248 255 / 15%); --color-border-media: rgb(252 248 255 / 15%);
--color-border-verified: rgb(220, 3, 240);
--color-border-on-bg-secondary: var(--color-grey-200); --color-border-on-bg-secondary: var(--color-grey-200);
--color-border-on-bg-brand-softer: var(--color-indigo-200); --color-border-on-bg-brand-softer: var(--color-indigo-200);
--color-border-on-bg-error-softer: #{utils.css-alpha( --color-border-on-bg-error-softer: #{utils.css-alpha(

View File

@ -379,6 +379,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
def conversation_from_uri(uri) def conversation_from_uri(uri)
return nil if uri.nil? return nil if uri.nil?
return Conversation.find_by(id: OStatus::TagManager.instance.unique_tag_to_local_id(uri, 'Conversation')) if OStatus::TagManager.instance.local_id?(uri) return Conversation.find_by(id: OStatus::TagManager.instance.unique_tag_to_local_id(uri, 'Conversation')) if OStatus::TagManager.instance.local_id?(uri)
return ActivityPub::TagManager.instance.uri_to_resource(uri, Conversation) if ActivityPub::TagManager.instance.local_uri?(uri)
begin begin
Conversation.find_or_create_by!(uri: uri) Conversation.find_or_create_by!(uri: uri)

View File

@ -243,12 +243,6 @@ class ActivityPub::TagManager
!host.nil? && (::TagManager.instance.local_domain?(host) || ::TagManager.instance.web_domain?(host)) !host.nil? && (::TagManager.instance.local_domain?(host) || ::TagManager.instance.web_domain?(host))
end end
def uri_to_local_id(uri, param = :id)
path_params = Rails.application.routes.recognize_path(uri)
path_params[:username] = Rails.configuration.x.local_domain if path_params[:controller] == 'instance_actors'
path_params[param]
end
def uris_to_local_accounts(uris) def uris_to_local_accounts(uris)
usernames = [] usernames = []
ids = [] ids = []
@ -266,6 +260,14 @@ class ActivityPub::TagManager
uri_to_resource(uri, Account) uri_to_resource(uri, Account)
end end
def uri_to_local_conversation(uri)
path_params = Rails.application.routes.recognize_path(uri)
return unless path_params[:controller] == 'activitypub/contexts'
account_id, conversation_id = path_params[:id].split('-')
Conversation.find_by(parent_account_id: account_id, id: conversation_id)
end
def uri_to_resource(uri, klass) def uri_to_resource(uri, klass)
return if uri.nil? return if uri.nil?
@ -273,6 +275,8 @@ class ActivityPub::TagManager
case klass.name case klass.name
when 'Account' when 'Account'
uris_to_local_accounts([uri]).first uris_to_local_accounts([uri]).first
when 'Conversation'
uri_to_local_conversation(uri)
else else
StatusFinder.new(uri).status StatusFinder.new(uri).status
end end

View File

@ -11,16 +11,12 @@ class OStatus::TagManager
def unique_tag_to_local_id(tag, expected_type) def unique_tag_to_local_id(tag, expected_type)
return nil unless local_id?(tag) return nil unless local_id?(tag)
if ActivityPub::TagManager.instance.local_uri?(tag) matches = Regexp.new("objectId=([\\d]+):objectType=#{expected_type}").match(tag)
ActivityPub::TagManager.instance.uri_to_local_id(tag) matches[1] unless matches.nil?
else
matches = Regexp.new("objectId=([\\d]+):objectType=#{expected_type}").match(tag)
matches[1] unless matches.nil?
end
end end
def local_id?(id) def local_id?(id)
id.start_with?("tag:#{Rails.configuration.x.local_domain}") || ActivityPub::TagManager.instance.local_uri?(id) id.start_with?("tag:#{Rails.configuration.x.local_domain}")
end end
def uri_for(target) def uri_for(target)

View File

@ -18,7 +18,7 @@ class TagManager
return if domain.nil? return if domain.nil?
uri = Addressable::URI.new uri = Addressable::URI.new
uri.host = domain.delete_suffix('/') uri.host = domain.strip.delete_suffix('/')
uri.normalized_host uri.normalized_host
end end

View File

@ -43,6 +43,7 @@ class Collection < ApplicationRecord
scope :with_items, -> { includes(:collection_items).merge(CollectionItem.with_accounts) } scope :with_items, -> { includes(:collection_items).merge(CollectionItem.with_accounts) }
scope :with_tag, -> { includes(:tag) } scope :with_tag, -> { includes(:tag) }
scope :discoverable, -> { where(discoverable: true) }
def remote? def remote?
!local? !local?

View File

@ -156,7 +156,7 @@ class InitialStateSerializer < ActiveModel::Serializer
end end
def serialized_account(account) def serialized_account(account)
ActiveModelSerializers::SerializableResource.new(account, serializer: REST::AccountSerializer) ActiveModelSerializers::SerializableResource.new(account, serializer: REST::AccountSerializer, scope_name: :current_user, scope: object.current_account&.user)
end end
def instance_presenter def instance_presenter

View File

@ -71,6 +71,9 @@ class REST::InstanceSerializer < ActiveModel::Serializer
accounts: { accounts: {
max_featured_tags: FeaturedTag::LIMIT, max_featured_tags: FeaturedTag::LIMIT,
max_pinned_statuses: StatusPinValidator::PIN_LIMIT, max_pinned_statuses: StatusPinValidator::PIN_LIMIT,
max_profile_fields: Account::DEFAULT_FIELDS_SIZE,
profile_field_name_limit: Account::Field::MAX_CHARACTERS_LOCAL,
profile_field_value_limit: Account::Field::MAX_CHARACTERS_LOCAL,
}, },
statuses: { statuses: {

View File

@ -2162,6 +2162,7 @@ ca:
error: Hi ha hagut un problema al esborrar la teva clau de seguretat. Tornau-ho a provar. error: Hi ha hagut un problema al esborrar la teva clau de seguretat. Tornau-ho a provar.
success: La teva clau de seguretat s'ha esborrat correctament. success: La teva clau de seguretat s'ha esborrat correctament.
invalid_credential: Clau de seguretat invàlida invalid_credential: Clau de seguretat invàlida
nickname: Sobrenom
nickname_hint: Introdueix el sobrenom de la teva clau de seguretat nova nickname_hint: Introdueix el sobrenom de la teva clau de seguretat nova
not_enabled: Encara no has activat WebAuthn not_enabled: Encara no has activat WebAuthn
not_supported: Aquest navegador no suporta claus de seguretat not_supported: Aquest navegador no suporta claus de seguretat

View File

@ -175,7 +175,7 @@ de:
labels: labels:
account: account:
attribution_domains: Websites, die auf dich verweisen dürfen attribution_domains: Websites, die auf dich verweisen dürfen
discoverable: Profil und Beiträge in Suchalgorithmen berücksichtigen discoverable: Profil und Beiträge in Empfehlungsalgorithmen berücksichtigen
fields: fields:
name: Beschriftung name: Beschriftung
value: Inhalt value: Inhalt

View File

@ -40,7 +40,7 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@csstools/stylelint-formatter-github": "^1.0.0", "@csstools/stylelint-formatter-github": "^2.0.0",
"@dnd-kit/core": "^6.1.0", "@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
@ -188,7 +188,7 @@
"storybook": "^10.0.5", "storybook": "^10.0.5",
"stylelint": "^16.19.1", "stylelint": "^16.19.1",
"stylelint-config-prettier-scss": "^1.0.0", "stylelint-config-prettier-scss": "^1.0.0",
"stylelint-config-standard-scss": "^16.0.0", "stylelint-config-standard-scss": "^17.0.0",
"typescript": "~5.9.0", "typescript": "~5.9.0",
"typescript-eslint": "^8.45.0", "typescript-eslint": "^8.45.0",
"typescript-plugin-css-modules": "^5.2.0", "typescript-plugin-css-modules": "^5.2.0",

View File

@ -521,7 +521,7 @@ RSpec.describe ActivityPub::Activity::Create do
end end
end end
context 'with a reply' do context 'with a reply without explicitly setting a conversation' do
let(:original_status) { Fabricate(:status) } let(:original_status) { Fabricate(:status) }
let(:object_json) do let(:object_json) do
@ -543,6 +543,30 @@ RSpec.describe ActivityPub::Activity::Create do
end end
end end
context 'with a reply explicitly setting a conversation' do
let(:original_status) { Fabricate(:status) }
let(:object_json) do
build_object(
inReplyTo: ActivityPub::TagManager.instance.uri_for(original_status),
conversation: ActivityPub::TagManager.instance.uri_for(original_status.conversation),
context: ActivityPub::TagManager.instance.uri_for(original_status.conversation)
)
end
it 'creates status' do
expect { subject.perform }.to change(sender.statuses, :count).by(1)
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.thread).to eq original_status
expect(status.reply?).to be true
expect(status.in_reply_to_account).to eq original_status.account
expect(status.conversation).to eq original_status.conversation
end
end
context 'with mentions' do context 'with mentions' do
let(:recipient) { Fabricate(:account) } let(:recipient) { Fabricate(:account) }

View File

@ -629,14 +629,6 @@ RSpec.describe ActivityPub::TagManager do
end end
end end
describe '#uri_to_local_id' do
let(:account) { Fabricate(:account, id_scheme: :username_ap_id) }
it 'returns the local ID' do
expect(subject.uri_to_local_id(subject.uri_for(account), :username)).to eq account.username
end
end
describe '#uris_to_local_accounts' do describe '#uris_to_local_accounts' do
it 'returns the expected local accounts' do it 'returns the expected local accounts' do
account = Fabricate(:account) account = Fabricate(:account)

View File

@ -54,12 +54,44 @@ RSpec.describe TagManager do
end end
describe '#normalize_domain' do describe '#normalize_domain' do
it 'returns nil if the given parameter is nil' do subject { described_class.instance.normalize_domain(domain) }
expect(described_class.instance.normalize_domain(nil)).to be_nil
context 'with a nil value' do
let(:domain) { nil }
it { is_expected.to be_nil }
end end
it 'returns normalized domain' do context 'with a blank value' do
expect(described_class.instance.normalize_domain('DoMaIn.Example.com/')).to eq 'domain.example.com' let(:domain) { '' }
it { is_expected.to be_blank }
end
context 'with a mixed case string' do
let(:domain) { 'DoMaIn.Example.com' }
it { is_expected.to eq('domain.example.com') }
end
context 'with a trailing slash string' do
let(:domain) { 'domain.example.com/' }
it { is_expected.to eq('domain.example.com') }
end
context 'with a space padded string' do
let(:domain) { ' domain.example.com ' }
it { is_expected.to eq('domain.example.com') }
end
context 'with an invalid domain string' do
let(:domain) { ' !@#$@#$@$@# ' }
it 'raises invalid uri error' do
expect { subject }.to raise_error(Addressable::URI::InvalidURIError)
end
end end
end end

View File

@ -5,7 +5,7 @@ require 'rails_helper'
RSpec.describe InstanceModerationNote do RSpec.describe InstanceModerationNote do
describe 'chronological' do describe 'chronological' do
it 'returns the instance notes sorted by oldest first' do it 'returns the instance notes sorted by oldest first' do
instance = Instance.find_or_initialize_by(domain: TagManager.instance.normalize_domain('mastodon.example')) instance = Instance.find_or_initialize_by(domain: 'mastodon.example')
note1 = Fabricate(:instance_moderation_note, domain: instance.domain) note1 = Fabricate(:instance_moderation_note, domain: instance.domain)
note2 = Fabricate(:instance_moderation_note, domain: instance.domain) note2 = Fabricate(:instance_moderation_note, domain: instance.domain)

View File

@ -55,6 +55,32 @@ RSpec.describe 'Api::V1Alpha::Collections', feature: :collections do
) )
end end
end end
context 'when some collections are not discoverable' do
before do
Fabricate(:collection, account:, discoverable: false)
end
context 'when requesting user is a third party' do
it 'hides the collections that are not discoverable' do
subject
expect(response).to have_http_status(200)
expect(response.parsed_body.size).to eq 3
end
end
context 'when requesting user owns the collection' do
let(:account) { user.account }
it 'returns all collections, including the ones that are not discoverable' do
subject
expect(response).to have_http_status(200)
expect(response.parsed_body.size).to eq 4
end
end
end
end end
describe 'GET /api/v1_alpha/collections/:id' do describe 'GET /api/v1_alpha/collections/:id' do

107
yarn.lock
View File

@ -1926,12 +1926,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@csstools/stylelint-formatter-github@npm:^1.0.0": "@csstools/stylelint-formatter-github@npm:^2.0.0":
version: 1.0.0 version: 2.0.0
resolution: "@csstools/stylelint-formatter-github@npm:1.0.0" resolution: "@csstools/stylelint-formatter-github@npm:2.0.0"
peerDependencies: peerDependencies:
stylelint: ^16.6.0 stylelint: ^17.0.0
checksum: 10c0/2052c4e4d89656b2b4176a6d07508ef73278d33c24a7408a3555d07f26ec853f85da95525590c51751fb3150a2ebb5e3083d8200dc6597af2cd8e93198695269 checksum: 10c0/1eddcb749eb93efff2e2d7edb4405459bf558ceaa6d90e792408802f30c55e3482a4cead9e69fd651f04a927e863782fc6cf813c37433da9ff1f068910080a06
languageName: node languageName: node
linkType: hard linkType: hard
@ -2861,7 +2861,7 @@ __metadata:
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@mastodon/mastodon@workspace:." resolution: "@mastodon/mastodon@workspace:."
dependencies: dependencies:
"@csstools/stylelint-formatter-github": "npm:^1.0.0" "@csstools/stylelint-formatter-github": "npm:^2.0.0"
"@dnd-kit/core": "npm:^6.1.0" "@dnd-kit/core": "npm:^6.1.0"
"@dnd-kit/sortable": "npm:^10.0.0" "@dnd-kit/sortable": "npm:^10.0.0"
"@dnd-kit/utilities": "npm:^3.2.2" "@dnd-kit/utilities": "npm:^3.2.2"
@ -2992,7 +2992,7 @@ __metadata:
stringz: "npm:^2.1.0" stringz: "npm:^2.1.0"
stylelint: "npm:^16.19.1" stylelint: "npm:^16.19.1"
stylelint-config-prettier-scss: "npm:^1.0.0" stylelint-config-prettier-scss: "npm:^1.0.0"
stylelint-config-standard-scss: "npm:^16.0.0" stylelint-config-standard-scss: "npm:^17.0.0"
substring-trie: "npm:^1.0.2" substring-trie: "npm:^1.0.2"
tesseract.js: "npm:^7.0.0" tesseract.js: "npm:^7.0.0"
tiny-queue: "npm:^0.2.1" tiny-queue: "npm:^0.2.1"
@ -9327,13 +9327,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"known-css-properties@npm:^0.36.0":
version: 0.36.0
resolution: "known-css-properties@npm:0.36.0"
checksum: 10c0/098c8f956408a7ce26a639c2354e0184fb2bb2772bb7d1ba23192b6b6cf5818cbb8a0acfb4049705ea103d9916065703bc540fa084a6349fdb41bf745aada4bc
languageName: node
linkType: hard
"known-css-properties@npm:^0.37.0": "known-css-properties@npm:^0.37.0":
version: 0.37.0 version: 0.37.0
resolution: "known-css-properties@npm:0.37.0" resolution: "known-css-properties@npm:0.37.0"
@ -9695,10 +9688,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"mdn-data@npm:^2.21.0": "mdn-data@npm:^2.25.0":
version: 2.21.0 version: 2.26.0
resolution: "mdn-data@npm:2.21.0" resolution: "mdn-data@npm:2.26.0"
checksum: 10c0/cd26902551af2cc29f06f130893cb04bca9ee278939fce3ffbcb759497cc80d53a6f4abdef2ae2f3ed3c95ac8d651f53fc141defd580ebf4ae2f93aea325957b checksum: 10c0/e5f17f4dac247f3e260c081761628d371e23659a7ff13413f83f5bd7fd0f2d8317e72277bb77f0e13115041334ff728a5363db64aabaf376c0e1b0b31016d0b8
languageName: node languageName: node
linkType: hard linkType: hard
@ -10697,8 +10690,8 @@ __metadata:
linkType: hard linkType: hard
"pino@npm:^10.0.0": "pino@npm:^10.0.0":
version: 10.2.0 version: 10.2.1
resolution: "pino@npm:10.2.0" resolution: "pino@npm:10.2.1"
dependencies: dependencies:
"@pinojs/redact": "npm:^0.4.0" "@pinojs/redact": "npm:^0.4.0"
atomic-sleep: "npm:^1.0.0" atomic-sleep: "npm:^1.0.0"
@ -10713,7 +10706,7 @@ __metadata:
thread-stream: "npm:^4.0.0" thread-stream: "npm:^4.0.0"
bin: bin:
pino: bin.js pino: bin.js
checksum: 10c0/8f88a2e205508d47ef04d2a6ec26ec450abb4b344d2d998d2e24b9e624e1a1ef7184f260ca5be06bc3733aa1ad76704657e373b359c7b71489a11709227e26da checksum: 10c0/2eaed48bb7fb8865e27ac6d6709383f5c117f1e59c818734c7cc22b362e9aa5846a0547e7fd9cde64088a3b48aa314e1dab07ee16da8dc3b87897970eb56843e
languageName: node languageName: node
linkType: hard linkType: hard
@ -13201,74 +13194,74 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"stylelint-config-recommended-scss@npm:^16.0.1": "stylelint-config-recommended-scss@npm:^17.0.0":
version: 16.0.2 version: 17.0.0
resolution: "stylelint-config-recommended-scss@npm:16.0.2" resolution: "stylelint-config-recommended-scss@npm:17.0.0"
dependencies: dependencies:
postcss-scss: "npm:^4.0.9" postcss-scss: "npm:^4.0.9"
stylelint-config-recommended: "npm:^17.0.0" stylelint-config-recommended: "npm:^18.0.0"
stylelint-scss: "npm:^6.12.1" stylelint-scss: "npm:^7.0.0"
peerDependencies: peerDependencies:
postcss: ^8.3.3 postcss: ^8.3.3
stylelint: ^16.24.0 stylelint: ^17.0.0
peerDependenciesMeta: peerDependenciesMeta:
postcss: postcss:
optional: true optional: true
checksum: 10c0/d4e30a881e248d8b039347bf967526f6afe6d6a07f18e2747e14568de32273e819ba478be7a61a0dd63178931b4e891050a34e73d296ab533aa434209a7f3146 checksum: 10c0/05b2e8d4316c2a8cc66eed0a2a8f01237e0ee8966a2e73d0b3c6706694f7630be165daa5a0cef511bc51f7e3fcb07a84c55d948c15fe6193a7e13cf9bb67c913
languageName: node languageName: node
linkType: hard linkType: hard
"stylelint-config-recommended@npm:^17.0.0": "stylelint-config-recommended@npm:^18.0.0":
version: 18.0.0
resolution: "stylelint-config-recommended@npm:18.0.0"
peerDependencies:
stylelint: ^17.0.0
checksum: 10c0/c7f8ff45c76ec23f4c8c0438894726976fd5e872c59d489f959b728d9879bba20dbf0040cd29ad3bbc00eb32befd95f5b6ca150002bb8aea74b0797bc42ccc17
languageName: node
linkType: hard
"stylelint-config-standard-scss@npm:^17.0.0":
version: 17.0.0 version: 17.0.0
resolution: "stylelint-config-recommended@npm:17.0.0" resolution: "stylelint-config-standard-scss@npm:17.0.0"
peerDependencies:
stylelint: ^16.23.0
checksum: 10c0/49e5d1c0f58197b2c5585b85fad814fed9bdec44c9870368c46a762664c5ff158c1145b6337456ae194409d692992b5b87421d62880422f71d8a3360417f5ad1
languageName: node
linkType: hard
"stylelint-config-standard-scss@npm:^16.0.0":
version: 16.0.0
resolution: "stylelint-config-standard-scss@npm:16.0.0"
dependencies: dependencies:
stylelint-config-recommended-scss: "npm:^16.0.1" stylelint-config-recommended-scss: "npm:^17.0.0"
stylelint-config-standard: "npm:^39.0.0" stylelint-config-standard: "npm:^40.0.0"
peerDependencies: peerDependencies:
postcss: ^8.3.3 postcss: ^8.3.3
stylelint: ^16.23.1 stylelint: ^17.0.0
peerDependenciesMeta: peerDependenciesMeta:
postcss: postcss:
optional: true optional: true
checksum: 10c0/eb77f23824c5d649b193cb71d7f9b538b32b8cc1769451b2993270361127243d4011baf891ec265711b8e34e69ce28acb57ab6c3947b51fa3713ac26f4276439 checksum: 10c0/0506537ba896f3d5e0fb002608090fcb41aa8ba7b65f1de8533702ce7c70e3f92b275782788a8356b5b687c86c53468c223e082226dda62780294b1cba324a36
languageName: node languageName: node
linkType: hard linkType: hard
"stylelint-config-standard@npm:^39.0.0": "stylelint-config-standard@npm:^40.0.0":
version: 39.0.1 version: 40.0.0
resolution: "stylelint-config-standard@npm:39.0.1" resolution: "stylelint-config-standard@npm:40.0.0"
dependencies: dependencies:
stylelint-config-recommended: "npm:^17.0.0" stylelint-config-recommended: "npm:^18.0.0"
peerDependencies: peerDependencies:
stylelint: ^16.23.0 stylelint: ^17.0.0
checksum: 10c0/70a9862a2cedcc2a1807bd92fc91c40877270cf8a39576b91ae056d6de51d3b68104b26f71056ff22461b4319e9ec988d009abf10ead513b2ec15569d82e865a checksum: 10c0/d8942552d53a3afda59b64d0c49503bb626fe5cef39a9e8c9583fcd60869f21431125ef4480ff27a59f7f2cf0da8af810d377129ef1d670ddc5def4defe2880c
languageName: node languageName: node
linkType: hard linkType: hard
"stylelint-scss@npm:^6.12.1": "stylelint-scss@npm:^7.0.0":
version: 6.12.1 version: 7.0.0
resolution: "stylelint-scss@npm:6.12.1" resolution: "stylelint-scss@npm:7.0.0"
dependencies: dependencies:
css-tree: "npm:^3.0.1" css-tree: "npm:^3.0.1"
is-plain-object: "npm:^5.0.0" is-plain-object: "npm:^5.0.0"
known-css-properties: "npm:^0.36.0" known-css-properties: "npm:^0.37.0"
mdn-data: "npm:^2.21.0" mdn-data: "npm:^2.25.0"
postcss-media-query-parser: "npm:^0.2.3" postcss-media-query-parser: "npm:^0.2.3"
postcss-resolve-nested-selector: "npm:^0.1.6" postcss-resolve-nested-selector: "npm:^0.1.6"
postcss-selector-parser: "npm:^7.1.0" postcss-selector-parser: "npm:^7.1.1"
postcss-value-parser: "npm:^4.2.0" postcss-value-parser: "npm:^4.2.0"
peerDependencies: peerDependencies:
stylelint: ^16.0.2 stylelint: ^16.8.2 || ^17.0.0
checksum: 10c0/9a0903d34be3c75a72bef32402899db5f6b94c0823c5944fdf1acb2c3dc61c1f70fbb322558f8cb7e42dd01ed5e0dec22ed298f03b7bacc9f467c28330acae71 checksum: 10c0/07d0f20c6bcb34b8b0b6bfb1d4367b4825b52a7eef7dde2adfbaec11ebc67242e6b99dccf70dfbef1eb0a9bf8712fe0ab49d183ff6e4cca9c7f89752f7e27027
languageName: node languageName: node
linkType: hard linkType: hard