Support role badges on Mastodon

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
environments/review-mastodon-b-cytv4i/deployments/4533
marcin mikołajczak 2024-04-05 17:12:12 +02:00
rodzic 35620a7330
commit e3a87a0326
4 zmienionych plików z 39 dodań i 10 usunięć

Wyświetl plik

@ -1,18 +1,32 @@
import clsx from 'clsx'; import clsx from 'clsx';
import React from 'react'; import React, { useMemo } from 'react';
import { hexToHsl } from 'soapbox/utils/theme';
interface IBadge { interface IBadge {
title: React.ReactNode; title: React.ReactNode;
slug: string; slug: string;
color?: string;
} }
/** Badge to display on a user's profile. */ /** Badge to display on a user's profile. */
const Badge: React.FC<IBadge> = ({ title, slug }) => { const Badge: React.FC<IBadge> = ({ title, slug, color }) => {
const fallback = !['patron', 'admin', 'moderator', 'opaque', 'badge:donor'].includes(slug); const fallback = !['patron', 'admin', 'moderator', 'opaque', 'badge:donor'].includes(slug);
const isDark = useMemo(() => {
const hsl = hexToHsl(color!);
if (hsl && hsl.l > 50) return false;
return true;
}, [color]);
return ( return (
<span <span
data-testid='badge' data-testid='badge'
className={clsx('inline-flex items-center rounded px-2 py-0.5 text-xs font-medium', { className={clsx('inline-flex items-center rounded px-2 py-0.5 text-xs font-medium', color ? {
'bg-gray-100 text-gray-100': isDark,
'bg-gray-800 text-gray-900': !isDark,
} : {
'bg-fuchsia-700 text-white': slug === 'patron', 'bg-fuchsia-700 text-white': slug === 'patron',
'bg-emerald-800 text-white': slug === 'badge:donor', 'bg-emerald-800 text-white': slug === 'badge:donor',
'bg-black text-white': slug === 'admin', 'bg-black text-white': slug === 'admin',
@ -20,6 +34,7 @@ const Badge: React.FC<IBadge> = ({ title, slug }) => {
'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100': fallback, 'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100': fallback,
'bg-white/75 text-gray-900': slug === 'opaque', 'bg-white/75 text-gray-900': slug === 'opaque',
})} })}
style={color ? { background: color } : undefined}
> >
{title} {title}
</span> </span>

Wyświetl plik

@ -7,7 +7,6 @@ import Markup from 'soapbox/components/markup';
import { dateFormatOptions } from 'soapbox/components/relative-timestamp'; import { dateFormatOptions } from 'soapbox/components/relative-timestamp';
import { Icon, HStack, Stack, Text } from 'soapbox/components/ui'; import { Icon, HStack, Stack, Text } from 'soapbox/components/ui';
import { useAppSelector, useSoapboxConfig } from 'soapbox/hooks'; import { useAppSelector, useSoapboxConfig } from 'soapbox/hooks';
import { badgeToTag, getBadges as getAccountBadges } from 'soapbox/utils/badges';
import { capitalize } from 'soapbox/utils/strings'; import { capitalize } from 'soapbox/utils/strings';
import ProfileFamiliarFollowers from './profile-familiar-followers'; import ProfileFamiliarFollowers from './profile-familiar-followers';
@ -58,13 +57,14 @@ const ProfileInfoPanel: React.FC<IProfileInfoPanel> = ({ account, username }) =>
}; };
const getCustomBadges = (): React.ReactNode[] => { const getCustomBadges = (): React.ReactNode[] => {
const badges = account ? getAccountBadges(account) : []; const badges = account?.roles || [];
return badges.map(badge => ( return badges.filter(badge => badge.highlighted).map(badge => (
<Badge <Badge
key={badge} key={badge.id || badge.name}
slug={badge} slug={badge.name}
title={capitalize(badgeToTag(badge))} title={capitalize(badge.name)}
color={badge.color}
/> />
)); ));
}; };

Wyświetl plik

@ -17,12 +17,21 @@ const headerMissing = require('soapbox/assets/images/header-missing.png');
const birthdaySchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/); const birthdaySchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/);
const hexSchema = z.string().regex(/^#[a-f0-9]{6}$/i);
const fieldSchema = z.object({ const fieldSchema = z.object({
name: z.string(), name: z.string(),
value: z.string(), value: z.string(),
verified_at: z.string().datetime().nullable().catch(null), verified_at: z.string().datetime().nullable().catch(null),
}); });
const roleSchema = z.object({
id: z.string().catch(''),
name: z.string().catch(''),
color: hexSchema.catch(''),
highlighted: z.boolean().catch(true),
});
const baseAccountSchema = z.object({ const baseAccountSchema = z.object({
acct: z.string().catch(''), acct: z.string().catch(''),
avatar: z.string().catch(avatarMissing), avatar: z.string().catch(avatarMissing),
@ -85,6 +94,7 @@ const baseAccountSchema = z.object({
relationship: relationshipSchema.optional().catch(undefined), relationship: relationshipSchema.optional().catch(undefined),
tags: z.array(z.string()).catch([]), tags: z.array(z.string()).catch([]),
}).optional().catch(undefined), }).optional().catch(undefined),
roles: filteredArray(roleSchema),
source: z.object({ source: z.object({
approved: z.boolean().catch(true), approved: z.boolean().catch(true),
chats_onboarded: z.boolean().catch(true), chats_onboarded: z.boolean().catch(true),
@ -118,6 +128,9 @@ const getDomain = (url: string) => {
} }
}; };
const filterBadges = (tags?: string[]) =>
tags?.filter(tag => tag.startsWith('badge:')).map(tag => ({ id: tag, name: tag.replace(/^badge:/, '') }));
/** Add internal fields to the account. */ /** Add internal fields to the account. */
const transformAccount = <T extends TransformableAccount>({ pleroma, other_settings, fields, ...account }: T) => { const transformAccount = <T extends TransformableAccount>({ pleroma, other_settings, fields, ...account }: T) => {
const customEmojiMap = makeCustomEmojiMap(account.emojis); const customEmojiMap = makeCustomEmojiMap(account.emojis);
@ -156,6 +169,7 @@ const transformAccount = <T extends TransformableAccount>({ pleroma, other_setti
const { relationship, ...rest } = pleroma; const { relationship, ...rest } = pleroma;
return rest; return rest;
})(), })(),
roles: account.roles || filterBadges(pleroma?.tags),
relationship: pleroma?.relationship, relationship: pleroma?.relationship,
staff: pleroma?.is_admin || pleroma?.is_moderator || false, staff: pleroma?.is_admin || pleroma?.is_moderator || false,
suspended: account.suspended || pleroma?.deactivated || false, suspended: account.suspended || pleroma?.deactivated || false,

Wyświetl plik

@ -117,7 +117,7 @@ export const generateThemeCss = (soapboxConfig: SoapboxConfig): string => {
return colorsToCss(soapboxConfig.colors.toJS() as TailwindColorPalette); return colorsToCss(soapboxConfig.colors.toJS() as TailwindColorPalette);
}; };
const hexToHsl = (hex: string): Hsl | null => { export const hexToHsl = (hex: string): Hsl | null => {
const rgb = hexToRgb(hex); const rgb = hexToRgb(hex);
return rgb ? rgbToHsl(rgb) : null; return rgb ? rgbToHsl(rgb) : null;
}; };