Merge remote-tracking branch 'soapbox/develop' into lexical

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2023-04-02 12:03:35 +02:00
commit 46ffd053bb
67 changed files with 480 additions and 328 deletions

View file

@ -49,6 +49,7 @@ const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL';
const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS'; const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS';
const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO'; const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO';
const COMPOSE_GROUP_POST = 'COMPOSE_GROUP_POST'; const COMPOSE_GROUP_POST = 'COMPOSE_GROUP_POST';
const COMPOSE_SET_GROUP_TIMELINE_VISIBLE = 'COMPOSE_SET_GROUP_TIMELINE_VISIBLE';
const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR'; const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR';
const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY'; const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY';
@ -298,7 +299,10 @@ const submitCompose = (composeId: string, routerHistory?: History, force = false
to, to,
}; };
if (compose.privacy === 'group') params.group_id = compose.group_id; if (compose.privacy === 'group') {
params.group_id = compose.group_id;
params.group_timeline_visible = compose.group_timeline_visible; // Truth Social
}
dispatch(createStatus(params, idempotencyKey, statusId)).then(function(data) { dispatch(createStatus(params, idempotencyKey, statusId)).then(function(data) {
if (!statusId && data.visibility === 'direct' && getState().conversations.mounted <= 0 && routerHistory) { if (!statusId && data.visibility === 'direct' && getState().conversations.mounted <= 0 && routerHistory) {
@ -489,6 +493,12 @@ const groupCompose = (composeId: string, groupId: string) =>
}); });
}; };
const setGroupTimelineVisible = (composeId: string, groupTimelineVisible: boolean) => ({
type: COMPOSE_SET_GROUP_TIMELINE_VISIBLE,
id: composeId,
groupTimelineVisible,
});
const clearComposeSuggestions = (composeId: string) => { const clearComposeSuggestions = (composeId: string) => {
if (cancelFetchComposeSuggestionsAccounts) { if (cancelFetchComposeSuggestionsAccounts) {
cancelFetchComposeSuggestionsAccounts(); cancelFetchComposeSuggestionsAccounts();
@ -805,6 +815,7 @@ export {
COMPOSE_REMOVE_FROM_MENTIONS, COMPOSE_REMOVE_FROM_MENTIONS,
COMPOSE_SET_STATUS, COMPOSE_SET_STATUS,
COMPOSE_EDITOR_STATE_SET, COMPOSE_EDITOR_STATE_SET,
COMPOSE_SET_GROUP_TIMELINE_VISIBLE,
setComposeToStatus, setComposeToStatus,
changeCompose, changeCompose,
replyCompose, replyCompose,
@ -831,6 +842,7 @@ export {
uploadComposeFail, uploadComposeFail,
undoUploadCompose, undoUploadCompose,
groupCompose, groupCompose,
setGroupTimelineVisible,
clearComposeSuggestions, clearComposeSuggestions,
fetchComposeSuggestions, fetchComposeSuggestions,
readyComposeSuggestionsEmojis, readyComposeSuggestionsEmojis,

View file

@ -193,7 +193,7 @@ const Account = ({
<Avatar src={account.avatar} size={avatarSize} /> <Avatar src={account.avatar} size={avatarSize} />
{emoji && ( {emoji && (
<Emoji <Emoji
className='absolute bottom-0 -right-1.5 h-5 w-5' className='absolute -right-1.5 bottom-0 h-5 w-5'
emoji={emoji} emoji={emoji}
src={emojiUrl} src={emojiUrl}
/> />

View file

@ -69,7 +69,7 @@ const DropdownMenuItem = ({ index, item, onClick }: IDropdownMenuItem) => {
}, [itemRef.current, index]); }, [itemRef.current, index]);
if (item === null) { if (item === null) {
return <li className='my-1 mx-2 h-[2px] bg-gray-100 dark:bg-gray-800' />; return <li className='mx-2 my-1 h-[2px] bg-gray-100 dark:bg-gray-800' />;
} }
return ( return (

View file

@ -113,7 +113,7 @@ class ErrorBoundary extends React.PureComponent<Props, State> {
const errorText = this.getErrorText(); const errorText = this.getErrorText();
return ( return (
<div className='flex h-screen flex-col bg-white pt-16 pb-12 dark:bg-primary-900'> <div className='flex h-screen flex-col bg-white pb-12 pt-16 dark:bg-primary-900'>
<main className='mx-auto flex w-full max-w-7xl grow flex-col justify-center px-4 sm:px-6 lg:px-8'> <main className='mx-auto flex w-full max-w-7xl grow flex-col justify-center px-4 sm:px-6 lg:px-8'>
<div className='flex shrink-0 justify-center'> <div className='flex shrink-0 justify-center'>
<a href='/' className='inline-flex'> <a href='/' className='inline-flex'>

View file

@ -52,7 +52,7 @@ const EventPreview: React.FC<IEventPreview> = ({ status, className, hideAction,
return ( return (
<div className={clsx('relative w-full overflow-hidden rounded-lg bg-gray-100 dark:bg-primary-800', className)}> <div className={clsx('relative w-full overflow-hidden rounded-lg bg-gray-100 dark:bg-primary-800', className)}>
<div className='absolute top-28 right-3'> <div className='absolute right-3 top-28'>
{floatingAction && action} {floatingAction && action}
</div> </div>
<div className='h-40 bg-primary-200 dark:bg-gray-600'> <div className='h-40 bg-primary-200 dark:bg-gray-600'>
@ -65,7 +65,7 @@ const EventPreview: React.FC<IEventPreview> = ({ status, className, hideAction,
{!floatingAction && action} {!floatingAction && action}
</HStack> </HStack>
<div className='flex flex-wrap gap-y-1 gap-x-2 text-gray-700 dark:text-gray-600'> <div className='flex flex-wrap gap-x-2 gap-y-1 text-gray-700 dark:text-gray-600'>
<HStack alignItems='center' space={2}> <HStack alignItems='center' space={2}>
<Icon src={require('@tabler/icons/user.svg')} /> <Icon src={require('@tabler/icons/user.svg')} />
<HStack space={1} alignItems='center' grow> <HStack space={1} alignItems='center' grow>

View file

@ -31,7 +31,7 @@ const GdprBanner: React.FC = () => {
return ( return (
<Banner theme='opaque' className={clsx('transition-transform', { 'translate-y-full': slideout })}> <Banner theme='opaque' className={clsx('transition-transform', { 'translate-y-full': slideout })}>
<div className='flex flex-col space-y-4 rtl:space-x-reverse lg:flex-row lg:items-center lg:justify-between lg:space-y-0 lg:space-x-4'> <div className='flex flex-col space-y-4 rtl:space-x-reverse lg:flex-row lg:items-center lg:justify-between lg:space-x-4 lg:space-y-0'>
<Stack space={2}> <Stack space={2}>
<Text size='xl' weight='bold'> <Text size='xl' weight='bold'>
<FormattedMessage id='gdpr.title' defaultMessage='{siteTitle} uses cookies' values={{ siteTitle: instance.title }} /> <FormattedMessage id='gdpr.title' defaultMessage='{siteTitle} uses cookies' values={{ siteTitle: instance.title }} />

View file

@ -37,7 +37,7 @@ const GroupCard: React.FC<IGroupCard> = ({ group }) => {
</Stack> </Stack>
{/* Group Avatar */} {/* Group Avatar */}
<div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'> <div className='absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'>
<GroupAvatar group={group} size={64} withRing /> <GroupAvatar group={group} size={64} withRing />
</div> </div>

View file

@ -23,8 +23,9 @@ const GroupAvatar = (props: IGroupAvatar) => {
className={ className={
clsx('relative rounded-full', { clsx('relative rounded-full', {
'shadow-[0_0_0_2px_theme(colors.primary.600),0_0_0_4px_theme(colors.white)]': isOwner && withRing, 'shadow-[0_0_0_2px_theme(colors.primary.600),0_0_0_4px_theme(colors.white)]': isOwner && withRing,
'dark:shadow-[0_0_0_2px_theme(colors.primary.600),0_0_0_4px_theme(colors.gray.800)]': isOwner && withRing,
'shadow-[0_0_0_2px_theme(colors.primary.600)]': isOwner && !withRing, 'shadow-[0_0_0_2px_theme(colors.primary.600)]': isOwner && !withRing,
'shadow-[0_0_0_2px_theme(colors.white)]': !isOwner && withRing, 'shadow-[0_0_0_2px_theme(colors.white)] dark:shadow-[0_0_0_2px_theme(colors.gray.800)]': !isOwner && withRing,
}) })
} }
src={group.avatar} src={group.avatar}

View file

@ -53,7 +53,7 @@ const GroupPopover = (props: IGroupPopoverContainer) => {
</Stack> </Stack>
{/* Group Avatar */} {/* Group Avatar */}
<div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'> <div className='absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'>
<GroupAvatar group={group} size={64} withRing /> <GroupAvatar group={group} size={64} withRing />
</div> </div>

View file

@ -16,7 +16,7 @@ const IconWithCounter: React.FC<IIconWithCounter> = ({ icon, count, countMax, ..
<Icon id={icon} {...rest as IIcon} /> <Icon id={icon} {...rest as IIcon} />
{count > 0 && ( {count > 0 && (
<span className='absolute -top-2 -right-3'> <span className='absolute -right-3 -top-2'>
<Counter count={count} countMax={countMax} /> <Counter count={count} countMax={countMax} />
</span> </span>
)} )}

View file

@ -142,7 +142,7 @@ export const ProfileHoverCard: React.FC<IProfileHoverCard> = ({ visible = true }
</Stack> </Stack>
{followedBy && ( {followedBy && (
<div className='absolute top-2 left-2'> <div className='absolute left-2 top-2'>
<Badge <Badge
slug='opaque' slug='opaque'
title={<FormattedMessage id='account.follows_you' defaultMessage='Follows you' />} title={<FormattedMessage id='account.follows_you' defaultMessage='Follows you' />}

View file

@ -166,7 +166,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
src={require('@tabler/icons/x.svg')} src={require('@tabler/icons/x.svg')}
ref={closeButtonRef} ref={closeButtonRef}
iconClassName='h-6 w-6' iconClassName='h-6 w-6'
className='absolute top-0 right-0 -mr-11 mt-2 text-gray-600 hover:text-gray-600 dark:text-gray-400 dark:hover:text-gray-300' className='absolute right-0 top-0 -mr-11 mt-2 text-gray-600 hover:text-gray-600 dark:text-gray-400 dark:hover:text-gray-300'
/> />
<div className='relative h-full w-full overflow-auto overflow-y-scroll'> <div className='relative h-full w-full overflow-auto overflow-y-scroll'>

View file

@ -70,7 +70,7 @@ const StillImage: React.FC<IStillImage> = ({ alt, className, src, style, letterb
)} )}
{(hoverToPlay && showExt) && ( {(hoverToPlay && showExt) && (
<div className='pointer-events-none absolute left-2 bottom-2 opacity-90 group-hover:hidden'> <div className='pointer-events-none absolute bottom-2 left-2 opacity-90 group-hover:hidden'>
<ExtensionBadge ext='GIF' /> <ExtensionBadge ext='GIF' />
</div> </div>
)} )}

View file

@ -9,7 +9,7 @@ const themes = {
'bg-transparent border-gray-400 dark:border-gray-800 hover:border-primary-300 dark:hover:border-primary-700 focus:border-primary-500 text-gray-900 dark:text-gray-100 focus:ring-primary-500', 'bg-transparent border-gray-400 dark:border-gray-800 hover:border-primary-300 dark:hover:border-primary-700 focus:border-primary-500 text-gray-900 dark:text-gray-100 focus:ring-primary-500',
accent: 'border-transparent bg-secondary-500 hover:bg-secondary-400 focus:bg-secondary-500 text-gray-100 focus:ring-secondary-300', accent: 'border-transparent bg-secondary-500 hover:bg-secondary-400 focus:bg-secondary-500 text-gray-100 focus:ring-secondary-300',
danger: 'border-transparent bg-danger-100 dark:bg-danger-900 text-danger-600 dark:text-danger-200 hover:bg-danger-600 hover:text-gray-100 dark:hover:text-gray-100 dark:hover:bg-danger-500 focus:ring-danger-500', danger: 'border-transparent bg-danger-100 dark:bg-danger-900 text-danger-600 dark:text-danger-200 hover:bg-danger-600 hover:text-gray-100 dark:hover:text-gray-100 dark:hover:bg-danger-500 focus:ring-danger-500',
transparent: 'border-transparent text-gray-800 backdrop-blur-sm bg-white/75 hover:bg-white/80', transparent: 'border-transparent bg-transparent text-primary-600 dark:text-accent-blue dark:bg-transparent hover:bg-gray-200 dark:hover:bg-gray-800/50',
outline: 'border-gray-100 border-2 bg-transparent text-gray-100 hover:bg-white/10', outline: 'border-gray-100 border-2 bg-transparent text-gray-100 hover:bg-white/10',
muted: 'border border-solid bg-transparent border-gray-400 dark:border-gray-800 hover:border-primary-300 dark:hover:border-primary-700 focus:border-primary-500 text-gray-900 dark:text-gray-100 focus:ring-primary-500', muted: 'border border-solid bg-transparent border-gray-400 dark:border-gray-800 hover:border-primary-300 dark:hover:border-primary-700 focus:border-primary-500 text-gray-900 dark:text-gray-100 focus:ring-primary-500',
}; };

View file

@ -8,7 +8,7 @@ const FileInput = forwardRef<HTMLInputElement, IFileInput>((props, ref) => {
{...props} {...props}
ref={ref} ref={ref}
type='file' type='file'
className='block w-full text-sm text-gray-800 file:mr-2 file:cursor-pointer file:rounded-full file:border file:border-solid file:border-gray-200 file:bg-white file:py-1.5 file:px-3 file:text-xs file:font-medium file:leading-4 file:text-gray-700 hover:file:bg-gray-100 dark:text-gray-200 dark:file:border-gray-800 dark:file:bg-gray-900 dark:file:text-gray-500 dark:file:hover:bg-gray-800' className='block w-full text-sm text-gray-800 file:mr-2 file:cursor-pointer file:rounded-full file:border file:border-solid file:border-gray-200 file:bg-white file:px-3 file:py-1.5 file:text-xs file:font-medium file:leading-4 file:text-gray-700 hover:file:bg-gray-100 dark:text-gray-200 dark:file:border-gray-800 dark:file:bg-gray-900 dark:file:text-gray-500 dark:file:hover:bg-gray-800'
/> />
); );
}); });

View file

@ -23,7 +23,7 @@ interface IIcon extends Pick<React.SVGAttributes<SVGAElement>, 'strokeWidth'> {
const Icon: React.FC<IIcon> = ({ src, alt, count, size, countMax, ...filteredProps }): JSX.Element => ( const Icon: React.FC<IIcon> = ({ src, alt, count, size, countMax, ...filteredProps }): JSX.Element => (
<div className='relative flex shrink-0 flex-col' data-testid='icon'> <div className='relative flex shrink-0 flex-col' data-testid='icon'>
{count ? ( {count ? (
<span className='absolute -top-2 -right-3 flex h-5 min-w-[20px] shrink-0 items-center justify-center whitespace-nowrap break-words'> <span className='absolute -right-3 -top-2 flex h-5 min-w-[20px] shrink-0 items-center justify-center whitespace-nowrap break-words'>
<Counter count={count} countMax={countMax} /> <Counter count={count} countMax={countMax} />
</span> </span>
) : null} ) : null}

View file

@ -37,6 +37,6 @@ const MenuList: React.FC<IMenuList> = (props) => {
}; };
/** Divides menu items. */ /** Divides menu items. */
const MenuDivider = () => <hr className='my-1 mx-2 border-t-2 border-gray-100 dark:border-gray-800' />; const MenuDivider = () => <hr className='mx-2 my-1 border-t-2 border-gray-100 dark:border-gray-800' />;
export { Menu, MenuButton, MenuDivider, MenuItems, MenuItem, MenuList, MenuLink }; export { Menu, MenuButton, MenuDivider, MenuItems, MenuItem, MenuList, MenuLink };

View file

@ -13,6 +13,8 @@ import {
import clsx from 'clsx'; import clsx from 'clsx';
import React, { useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
import Portal from '../portal/portal';
interface IPopover { interface IPopover {
children: React.ReactElement<any, string | React.JSXElementConstructor<any>> children: React.ReactElement<any, string | React.JSXElementConstructor<any>>
/** The content of the popover */ /** The content of the popover */
@ -83,31 +85,33 @@ const Popover: React.FC<IPopover> = (props) => {
})} })}
{(isMounted) && ( {(isMounted) && (
<div <Portal>
ref={refs.setFloating} <div
style={{ ref={refs.setFloating}
position: strategy, style={{
top: y ?? 0, position: strategy,
left: x ?? 0, top: y ?? 0,
...styles, left: x ?? 0,
}} ...styles,
className={ }}
clsx({ className={
'z-40 rounded-lg bg-white shadow-2xl dark:bg-gray-900 dark:ring-2 dark:ring-primary-700': true, clsx({
'p-6': !isFlush, 'z-40 rounded-lg bg-white shadow-2xl dark:bg-gray-900 dark:ring-2 dark:ring-primary-700': true,
}) 'p-6': !isFlush,
} })
{...getFloatingProps()} }
> {...getFloatingProps()}
{content} >
{content}
<FloatingArrow <FloatingArrow
ref={arrowRef} ref={arrowRef}
context={context} context={context}
className='-ml-2 fill-white dark:hidden' /** -ml-2 to fix offcenter arrow */ className='-ml-2 fill-white dark:hidden' /** -ml-2 to fix offcenter arrow */
tipRadius={3} tipRadius={3}
/> />
</div> </div>
</Portal>
)} )}
</> </>
); );

View file

@ -1,13 +1,52 @@
import React from 'react'; import clsx from 'clsx';
import ReactToggle, { ToggleProps } from 'react-toggle'; import React, { useRef } from 'react';
interface IToggle extends Pick<React.InputHTMLAttributes<HTMLInputElement>, 'id' | 'name' | 'checked' | 'onChange' | 'required' | 'disabled'> {
size?: 'sm' | 'md'
}
/** A glorified checkbox. */
const Toggle: React.FC<IToggle> = ({ id, size = 'md', name, checked, onChange, required, disabled }) => {
const input = useRef<HTMLInputElement>(null);
const handleClick: React.MouseEventHandler<HTMLButtonElement> = () => {
input.current?.focus();
input.current?.click();
};
/** A glorified checkbox. Wrapper around react-toggle. */
const Toggle: React.FC<ToggleProps> = ({ icons = false, ...rest }) => {
return ( return (
<ReactToggle <button
icons={icons} className={clsx('flex-none rounded-full', {
{...rest} 'bg-gray-500': !checked && !disabled,
/> 'bg-primary-600': checked && !disabled,
'bg-gray-200': !checked && disabled,
'bg-primary-200': checked && disabled,
'w-9 p-0.5': size === 'sm',
'w-11 p-0.5': size === 'md',
'cursor-default': disabled,
})}
onClick={handleClick}
>
<div className={clsx('rounded-full bg-white transition-transform', {
'h-4.5 w-4.5': size === 'sm',
'translate-x-3.5': size === 'sm' && checked,
'h-6 w-6': size === 'md',
'translate-x-4': size === 'md' && checked,
})}
/>
<input
id={id}
ref={input}
name={name}
type='checkbox'
className='sr-only'
checked={checked}
onChange={onChange}
required={required}
disabled={disabled}
/>
</button>
); );
}; };

View file

@ -52,8 +52,8 @@ const MediaItem: React.FC<IMediaItem> = ({ attachment, onOpenMedia }) => {
} }
}; };
const status = attachment.get('status'); const status = attachment.status;
const title = status.get('spoiler_text') || attachment.get('description'); const title = status.spoiler_text || attachment.description;
let thumbnail: React.ReactNode = ''; let thumbnail: React.ReactNode = '';
let icon; let icon;
@ -61,8 +61,8 @@ const MediaItem: React.FC<IMediaItem> = ({ attachment, onOpenMedia }) => {
if (attachment.type === 'unknown') { if (attachment.type === 'unknown') {
// Skip // Skip
} else if (attachment.type === 'image') { } else if (attachment.type === 'image') {
const focusX = Number(attachment.getIn(['meta', 'focus', 'x'])) || 0; const focusX = Number(attachment.meta.getIn(['focus', 'x'])) || 0;
const focusY = Number(attachment.getIn(['meta', 'focus', 'y'])) || 0; const focusY = Number(attachment.meta.getIn(['focus', 'y'])) || 0;
const x = ((focusX / 2) + .5) * 100; const x = ((focusX / 2) + .5) * 100;
const y = ((focusY / -2) + .5) * 100; const y = ((focusY / -2) + .5) * 100;
@ -122,9 +122,9 @@ const MediaItem: React.FC<IMediaItem> = ({ attachment, onOpenMedia }) => {
return ( return (
<div className='col-span-1'> <div className='col-span-1'>
<a className='media-gallery__item-thumbnail aspect-square' href={status.get('url')} target='_blank' onClick={handleClick} title={title}> <a className='media-gallery__item-thumbnail aspect-1' href={status.url} target='_blank' onClick={handleClick} title={title}>
<Blurhash <Blurhash
hash={attachment.get('blurhash')} hash={attachment.blurhash}
className={clsx('media-gallery__preview', { className={clsx('media-gallery__preview', {
'media-gallery__preview--hidden': visible, 'media-gallery__preview--hidden': visible,
})} })}

View file

@ -617,7 +617,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
<div className='relative isolate flex h-32 w-full flex-col justify-center overflow-hidden bg-gray-200 dark:bg-gray-900/50 md:rounded-t-xl lg:h-48'> <div className='relative isolate flex h-32 w-full flex-col justify-center overflow-hidden bg-gray-200 dark:bg-gray-900/50 md:rounded-t-xl lg:h-48'>
{renderHeader()} {renderHeader()}
<div className='absolute top-2 left-2'> <div className='absolute left-2 top-2'>
<HStack alignItems='center' space={1}> <HStack alignItems='center' space={1}>
{info} {info}
</HStack> </HStack>

View file

@ -36,7 +36,7 @@ const AwaitingApproval: React.FC = () => {
className='divide-y divide-solid divide-gray-200 dark:divide-gray-800' className='divide-y divide-solid divide-gray-200 dark:divide-gray-800'
> >
{accountIds.map(id => ( {accountIds.map(id => (
<div key={id} className='py-4 px-5'> <div key={id} className='px-5 py-4'>
<UnapprovedAccount accountId={id} /> <UnapprovedAccount accountId={id} />
</div> </div>
))} ))}

View file

@ -113,7 +113,7 @@ const Ad: React.FC<IAd> = ({ ad }) => {
</Card> </Card>
{showInfo && ( {showInfo && (
<div ref={infobox} className='absolute top-5 right-5 max-w-[234px]'> <div ref={infobox} className='absolute right-5 top-5 max-w-[234px]'>
<Card variant='rounded'> <Card variant='rounded'>
<Stack space={2}> <Stack space={2}>
<Text size='sm' weight='bold'> <Text size='sm' weight='bold'>

View file

@ -37,7 +37,7 @@ const AuthLayout = () => {
<main className='relative h-full sm:flex sm:justify-center'> <main className='relative h-full sm:flex sm:justify-center'>
<div className='flex h-full w-full flex-col sm:max-w-lg md:max-w-2xl lg:max-w-6xl'> <div className='flex h-full w-full flex-col sm:max-w-lg md:max-w-2xl lg:max-w-6xl'>
<header className='relative mb-auto flex justify-between py-12 px-2'> <header className='relative mb-auto flex justify-between px-2 py-12'>
<div className='relative z-0 flex-1 px-2 lg:absolute lg:inset-0 lg:flex lg:items-center lg:justify-center'> <div className='relative z-0 flex-1 px-2 lg:absolute lg:inset-0 lg:flex lg:items-center lg:justify-center'>
<Link to='/' className='cursor-pointer'> <Link to='/' className='cursor-pointer'>
<SiteLogo alt={instance.title} className='h-7' /> <SiteLogo alt={instance.title} className='h-7' />

View file

@ -80,7 +80,7 @@ const ChatList: React.FC<IChatList> = ({ onClickChat, useWindowScroll = false, s
})} })}
/> />
<div <div
className={clsx('pointer-events-none absolute inset-x-0 bottom-0 flex justify-center rounded-b-lg bg-gradient-to-t from-white to-transparent pt-12 pb-8 transition-opacity duration-500 dark:from-gray-900', { className={clsx('pointer-events-none absolute inset-x-0 bottom-0 flex justify-center rounded-b-lg bg-gradient-to-t from-white to-transparent pb-8 pt-12 transition-opacity duration-500 dark:from-gray-900', {
'opacity-0': isNearBottom, 'opacity-0': isNearBottom,
'opacity-100': !isNearBottom, 'opacity-100': !isNearBottom,
})} })}

View file

@ -47,7 +47,7 @@ const ChatPageSettings = () => {
}; };
return ( return (
<Stack className='h-full space-y-8 py-6 px-4 sm:p-6'> <Stack className='h-full space-y-8 px-4 py-6 sm:p-6'>
<HStack alignItems='center'> <HStack alignItems='center'>
<IconButton <IconButton
src={require('@tabler/icons/arrow-left.svg')} src={require('@tabler/icons/arrow-left.svg')}

View file

@ -36,7 +36,7 @@ const Welcome = () => {
}; };
return ( return (
<Stack className='h-full overflow-y-auto py-20 px-4 sm:px-0' data-testid='chats-welcome'> <Stack className='h-full overflow-y-auto px-4 py-20 sm:px-0' data-testid='chats-welcome'>
<div className='mx-auto mb-2.5 w-full sm:w-3/5 xl:w-2/5'> <div className='mx-auto mb-2.5 w-full sm:w-3/5 xl:w-2/5'>
<Text align='center' weight='bold' className='mb-6 text-2xl leading-8 md:text-3xl'> <Text align='center' weight='bold' className='mb-6 text-2xl leading-8 md:text-3xl'>
{intl.formatMessage(messages.title, { br: <br /> })} {intl.formatMessage(messages.title, { br: <br /> })}

View file

@ -69,7 +69,7 @@ const Results = ({ accountSearchResult, onSelect }: IResults) => {
})} })}
/> />
<div <div
className={clsx('pointer-events-none absolute inset-x-0 bottom-0 flex justify-center rounded-b-lg bg-gradient-to-t from-white to-transparent pt-12 pb-8 transition-opacity duration-500 dark:from-gray-900', { className={clsx('pointer-events-none absolute inset-x-0 bottom-0 flex justify-center rounded-b-lg bg-gradient-to-t from-white to-transparent pb-8 pt-12 transition-opacity duration-500 dark:from-gray-900', {
'opacity-0': isNearBottom, 'opacity-0': isNearBottom,
'opacity-100': !isNearBottom, 'opacity-100': !isNearBottom,
})} })}

View file

@ -31,7 +31,7 @@ const ChatPaneHeader = (props: IChatPaneHeader) => {
} }
return ( return (
<HStack {...rest} alignItems='center' justifyContent='between' className='h-16 rounded-t-xl py-3 px-4'> <HStack {...rest} alignItems='center' justifyContent='between' className='h-16 rounded-t-xl px-4 py-3'>
<ButtonComp <ButtonComp
className='flex h-16 grow flex-row items-center space-x-1' className='flex h-16 grow flex-row items-center space-x-1'
data-testid='title' data-testid='title'

View file

@ -63,9 +63,10 @@ interface IComposeForm<ID extends string> {
clickableAreaRef?: React.RefObject<HTMLDivElement> clickableAreaRef?: React.RefObject<HTMLDivElement>
event?: string event?: string
group?: string group?: string
extra?: React.ReactNode
} }
const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickableAreaRef, event, group }: IComposeForm<ID>) => { const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickableAreaRef, event, group, extra }: IComposeForm<ID>) => {
const history = useHistory(); const history = useHistory();
const intl = useIntl(); const intl = useIntl();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -344,6 +345,8 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
<QuotedStatusContainer composeId={id} /> <QuotedStatusContainer composeId={id} />
{extra && <div className={clsx({ 'hidden': condensed })}>{extra}</div>}
<div <div
className={clsx('flex flex-wrap items-center justify-between', { className={clsx('flex flex-wrap items-center justify-between', {
'hidden': condensed, 'hidden': condensed,

View file

@ -31,7 +31,7 @@ const AccountCard: React.FC<IAccountCard> = ({ id }) => {
<div className='flex flex-col divide-y divide-gray-200 rounded-lg bg-white text-center shadow dark:divide-primary-700 dark:bg-primary-800'> <div className='flex flex-col divide-y divide-gray-200 rounded-lg bg-white text-center shadow dark:divide-primary-700 dark:bg-primary-800'>
<div className='relative'> <div className='relative'>
{followedBy && ( {followedBy && (
<div className='absolute top-2.5 left-2.5'> <div className='absolute left-2.5 top-2.5'>
<Badge <Badge
slug='opaque' slug='opaque'
title={<FormattedMessage id='account.follows_you' defaultMessage='Follows you' />} title={<FormattedMessage id='account.follows_you' defaultMessage='Follows you' />}
@ -59,7 +59,7 @@ const AccountCard: React.FC<IAccountCard> = ({ id }) => {
<Text <Text
truncate truncate
align='left' align='left'
className={clsx('[&_br]:hidden [&_p]:hidden [&_p:first-child]:inline [&_p:first-child]:truncate')} className={clsx('[&_br]:hidden [&_p:first-child]:inline [&_p:first-child]:truncate [&_p]:hidden')}
dangerouslySetInnerHTML={{ __html: account.note_emojified || '&nbsp;' }} dangerouslySetInnerHTML={{ __html: account.note_emojified || '&nbsp;' }}
/> />
</Stack> </Stack>

View file

@ -26,7 +26,7 @@ const ProfilePreview: React.FC<IProfilePreview> = ({ account }) => {
<Avatar className='bg-gray-400' src={account.avatar} /> <Avatar className='bg-gray-400' src={account.avatar} />
{account.verified && ( {account.verified && (
<div className='absolute -top-1.5 -right-1.5'> <div className='absolute -right-1.5 -top-1.5'>
<VerificationBadge /> <VerificationBadge />
</div> </div>
)} )}

View file

@ -71,7 +71,6 @@ const FilterField: StreamfieldComponent<IFilterField> = ({ value, onChange }) =>
<Toggle <Toggle
checked={value.whole_word} checked={value.whole_word}
onChange={handleChange('whole_word')} onChange={handleChange('whole_word')}
icons={false}
/> />
<Text tag='span' theme='muted'> <Text tag='span' theme='muted'>
@ -212,28 +211,24 @@ const EditFilter: React.FC<IEditFilter> = ({ params }) => {
<List> <List>
<ListItem label={intl.formatMessage(messages.home_timeline)}> <ListItem label={intl.formatMessage(messages.home_timeline)}>
<Toggle <Toggle
name='home_timeline'
checked={homeTimeline} checked={homeTimeline}
onChange={({ target }) => setHomeTimeline(target.checked)} onChange={({ target }) => setHomeTimeline(target.checked)}
/> />
</ListItem> </ListItem>
<ListItem label={intl.formatMessage(messages.public_timeline)}> <ListItem label={intl.formatMessage(messages.public_timeline)}>
<Toggle <Toggle
name='public_timeline'
checked={publicTimeline} checked={publicTimeline}
onChange={({ target }) => setPublicTimeline(target.checked)} onChange={({ target }) => setPublicTimeline(target.checked)}
/> />
</ListItem> </ListItem>
<ListItem label={intl.formatMessage(messages.notifications)}> <ListItem label={intl.formatMessage(messages.notifications)}>
<Toggle <Toggle
name='notifications'
checked={notifications} checked={notifications}
onChange={({ target }) => setNotifications(target.checked)} onChange={({ target }) => setNotifications(target.checked)}
/> />
</ListItem> </ListItem>
<ListItem label={intl.formatMessage(messages.conversations)}> <ListItem label={intl.formatMessage(messages.conversations)}>
<Toggle <Toggle
name='conversations'
checked={conversations} checked={conversations}
onChange={({ target }) => setConversations(target.checked)} onChange={({ target }) => setConversations(target.checked)}
/> />
@ -241,7 +236,6 @@ const EditFilter: React.FC<IEditFilter> = ({ params }) => {
{features.filtersV2 && ( {features.filtersV2 && (
<ListItem label={intl.formatMessage(messages.accounts)}> <ListItem label={intl.formatMessage(messages.accounts)}>
<Toggle <Toggle
name='accounts'
checked={accounts} checked={accounts}
onChange={({ target }) => setAccounts(target.checked)} onChange={({ target }) => setAccounts(target.checked)}
/> />
@ -255,7 +249,6 @@ const EditFilter: React.FC<IEditFilter> = ({ params }) => {
hint={intl.formatMessage(features.filtersV2 ? messages.hide_hint : messages.drop_hint)} hint={intl.formatMessage(features.filtersV2 ? messages.hide_hint : messages.drop_hint)}
> >
<Toggle <Toggle
name='hide'
checked={hide} checked={hide}
onChange={({ target }) => setHide(target.checked)} onChange={({ target }) => setHide(target.checked)}
/> />

View file

@ -0,0 +1,85 @@
import React from 'react';
import { buildGroup, buildGroupRelationship } from 'soapbox/jest/factory';
import { render, screen } from 'soapbox/jest/test-helpers';
import { GroupRoles } from 'soapbox/schemas/group-member';
import { Group } from 'soapbox/types/entities';
import GroupOptionsButton from '../group-options-button';
let group: Group;
describe('<GroupOptionsButton />', () => {
describe('when the user blocked', () => {
beforeEach(() => {
group = buildGroup({
relationship: buildGroupRelationship({
requested: false,
member: true,
blocked_by: true,
role: 'user',
}),
});
});
it('should render null', () => {
render(<GroupOptionsButton group={group} />);
expect(screen.queryAllByTestId('dropdown-menu-button')).toHaveLength(0);
});
});
describe('when the user is an admin', () => {
beforeEach(() => {
group = buildGroup({
relationship: buildGroupRelationship({
requested: false,
member: true,
role: GroupRoles.ADMIN,
}),
});
});
it('should render null', () => {
render(<GroupOptionsButton group={group} />);
expect(screen.queryAllByTestId('dropdown-menu-button')).toHaveLength(0);
});
});
describe('when the user is an owner', () => {
beforeEach(() => {
group = buildGroup({
relationship: buildGroupRelationship({
requested: false,
member: true,
role: GroupRoles.OWNER,
}),
});
});
it('should render null', () => {
render(<GroupOptionsButton group={group} />);
expect(screen.queryAllByTestId('dropdown-menu-button')).toHaveLength(0);
});
});
describe('when the user is a member', () => {
beforeEach(() => {
group = buildGroup({
relationship: buildGroupRelationship({
requested: false,
member: true,
role: GroupRoles.USER,
}),
});
});
it('should render the dropdown menu', () => {
render(<GroupOptionsButton group={group} />);
expect(screen.queryAllByTestId('dropdown-menu-button')).toHaveLength(1);
});
});
});

View file

@ -0,0 +1,66 @@
import React from 'react';
import { buildGroup, buildGroupRelationship } from 'soapbox/jest/factory';
import { render, screen } from 'soapbox/jest/test-helpers';
import { GroupRoles } from 'soapbox/schemas/group-member';
import { Group } from 'soapbox/types/entities';
import GroupRelationship from '../group-relationship';
let group: Group;
describe('<GroupRelationship />', () => {
describe('when the user is an admin', () => {
beforeEach(() => {
group = buildGroup({
relationship: buildGroupRelationship({
requested: false,
member: true,
role: GroupRoles.ADMIN,
}),
});
});
it('should render the relationship', () => {
render(<GroupRelationship group={group} />);
expect(screen.getByTestId('group-relationship')).toHaveTextContent('Admin');
});
});
describe('when the user is an owner', () => {
beforeEach(() => {
group = buildGroup({
relationship: buildGroupRelationship({
requested: false,
member: true,
role: GroupRoles.OWNER,
}),
});
});
it('should render the relationship', () => {
render(<GroupRelationship group={group} />);
expect(screen.getByTestId('group-relationship')).toHaveTextContent('Owner');
});
});
describe('when the user is a member', () => {
beforeEach(() => {
group = buildGroup({
relationship: buildGroupRelationship({
requested: false,
member: true,
role: GroupRoles.USER,
}),
});
});
it('should render null', () => {
render(<GroupRelationship group={group} />);
expect(screen.queryAllByTestId('group-relationship')).toHaveLength(0);
});
});
});

View file

@ -109,7 +109,7 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
<div className='relative'> <div className='relative'>
{renderHeader()} {renderHeader()}
<div className='absolute left-1/2 bottom-0 -translate-x-1/2 translate-y-1/2'> <div className='absolute bottom-0 left-1/2 -translate-x-1/2 translate-y-1/2'>
<a href={group.avatar} onClick={handleAvatarClick} target='_blank'> <a href={group.avatar} onClick={handleAvatarClick} target='_blank'>
<GroupAvatar <GroupAvatar
group={group} group={group}

View file

@ -1,4 +1,5 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { initReport, ReportableEntities } from 'soapbox/actions/reports'; import { initReport, ReportableEntities } from 'soapbox/actions/reports';
import DropdownMenu, { Menu } from 'soapbox/components/dropdown-menu'; import DropdownMenu, { Menu } from 'soapbox/components/dropdown-menu';
@ -8,20 +9,25 @@ import { GroupRoles } from 'soapbox/schemas/group-member';
import type { Account, Group } from 'soapbox/types/entities'; import type { Account, Group } from 'soapbox/types/entities';
const messages = defineMessages({
report: { id: 'group.report.label', defaultMessage: 'Report' },
});
interface IGroupActionButton { interface IGroupActionButton {
group: Group group: Group
} }
const GroupOptionsButton = ({ group }: IGroupActionButton) => { const GroupOptionsButton = ({ group }: IGroupActionButton) => {
const dispatch = useAppDispatch();
const account = useOwnAccount(); const account = useOwnAccount();
const dispatch = useAppDispatch();
const intl = useIntl();
const isMember = group.relationship?.role === GroupRoles.USER; const isMember = group.relationship?.role === GroupRoles.USER;
const isBlocked = group.relationship?.blocked_by; const isBlocked = group.relationship?.blocked_by;
const menu: Menu = useMemo(() => ([ const menu: Menu = useMemo(() => ([
{ {
text: 'Report', text: intl.formatMessage(messages.report),
icon: require('@tabler/icons/flag.svg'), icon: require('@tabler/icons/flag.svg'),
action: () => dispatch(initReport(ReportableEntities.GROUP, account as Account, { group })), action: () => dispatch(initReport(ReportableEntities.GROUP, account as Account, { group })),
}, },
@ -38,6 +44,7 @@ const GroupOptionsButton = ({ group }: IGroupActionButton) => {
theme='secondary' theme='secondary'
iconClassName='h-5 w-5' iconClassName='h-5 w-5'
className='self-stretch px-2.5' className='self-stretch px-2.5'
data-testid='dropdown-menu-button'
/> />
</DropdownMenu> </DropdownMenu>
); );

View file

@ -13,12 +13,17 @@ const GroupRelationship = ({ group }: IGroupRelationship) => {
const isOwner = group.relationship?.role === GroupRoles.OWNER; const isOwner = group.relationship?.role === GroupRoles.OWNER;
const isAdmin = group.relationship?.role === GroupRoles.ADMIN; const isAdmin = group.relationship?.role === GroupRoles.ADMIN;
if (!isOwner || !isAdmin) { if (!isOwner && !isAdmin) {
return null; return null;
} }
return ( return (
<HStack space={1} alignItems='center'> <HStack
space={1}
alignItems='center'
data-testid='group-relationship'
className='text-primary-600 dark:text-accent-blue'
>
<Icon <Icon
className='h-4 w-4' className='h-4 w-4'
src={ src={
@ -30,8 +35,8 @@ const GroupRelationship = ({ group }: IGroupRelationship) => {
<Text tag='span' weight='medium' size='sm' theme='inherit'> <Text tag='span' weight='medium' size='sm' theme='inherit'>
{isOwner {isOwner
? <FormattedMessage id='group.role.admin' defaultMessage='Admin' /> ? <FormattedMessage id='group.role.owner' defaultMessage='Owner' />
: <FormattedMessage id='group.role.moderator' defaultMessage='Moderator' />} : <FormattedMessage id='group.role.admin' defaultMessage='Admin' />}
</Text> </Text>
</HStack> </HStack>
); );

View file

@ -66,7 +66,7 @@ const HeaderPicker = React.forwardRef<HTMLInputElement, IMediaInput>(({ src, onC
const AvatarPicker = React.forwardRef<HTMLInputElement, IMediaInput>(({ src, onChange, accept, disabled }, ref) => { const AvatarPicker = React.forwardRef<HTMLInputElement, IMediaInput>(({ src, onChange, accept, disabled }, ref) => {
return ( return (
<label className='absolute left-1/2 bottom-0 h-20 w-20 -translate-x-1/2 translate-y-1/2 cursor-pointer rounded-full bg-primary-500 ring-2 ring-white dark:ring-primary-900'> <label className='absolute bottom-0 left-1/2 h-20 w-20 -translate-x-1/2 translate-y-1/2 cursor-pointer rounded-full bg-primary-500 ring-2 ring-white dark:ring-primary-900'>
{src && <Avatar src={src} size={80} />} {src && <Avatar src={src} size={80} />}
<HStack <HStack
alignItems='center' alignItems='center'

View file

@ -2,12 +2,12 @@ import React, { useEffect } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { groupCompose } from 'soapbox/actions/compose'; import { groupCompose, setGroupTimelineVisible } from 'soapbox/actions/compose';
import { connectGroupStream } from 'soapbox/actions/streaming'; import { connectGroupStream } from 'soapbox/actions/streaming';
import { expandGroupTimeline } from 'soapbox/actions/timelines'; import { expandGroupTimeline } from 'soapbox/actions/timelines';
import { Avatar, HStack, Icon, Stack, Text } from 'soapbox/components/ui'; import { Avatar, HStack, Icon, Stack, Text, Toggle } from 'soapbox/components/ui';
import ComposeForm from 'soapbox/features/compose/components/compose-form'; import ComposeForm from 'soapbox/features/compose/components/compose-form';
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks'; import { useAppDispatch, useAppSelector, useOwnAccount } from 'soapbox/hooks';
import { useGroup } from 'soapbox/hooks/api'; import { useGroup } from 'soapbox/hooks/api';
import Timeline from '../ui/components/timeline'; import Timeline from '../ui/components/timeline';
@ -26,16 +26,21 @@ const GroupTimeline: React.FC<IGroupTimeline> = (props) => {
const { group } = useGroup(groupId); const { group } = useGroup(groupId);
const composeId = `group:${groupId}`;
const canComposeGroupStatus = !!account && group?.relationship?.member; const canComposeGroupStatus = !!account && group?.relationship?.member;
const groupTimelineVisible = useAppSelector((state) => !!state.compose.get(composeId)?.group_timeline_visible);
const handleLoadMore = (maxId: string) => { const handleLoadMore = (maxId: string) => {
dispatch(expandGroupTimeline(groupId, { maxId })); dispatch(expandGroupTimeline(groupId, { maxId }));
}; };
const handleToggleChange = () => {
dispatch(setGroupTimelineVisible(composeId, !groupTimelineVisible));
};
useEffect(() => { useEffect(() => {
dispatch(expandGroupTimeline(groupId)); dispatch(expandGroupTimeline(groupId));
dispatch(groupCompose(composeId, groupId));
dispatch(groupCompose(`group:${groupId}`, groupId));
const disconnect = dispatch(connectGroupStream(groupId)); const disconnect = dispatch(connectGroupStream(groupId));
@ -58,10 +63,25 @@ const GroupTimeline: React.FC<IGroupTimeline> = (props) => {
</Link> </Link>
<ComposeForm <ComposeForm
id={`group:${groupId}`} id={composeId}
shouldCondense shouldCondense
autoFocus={false} autoFocus={false}
group={groupId} group={groupId}
extra={!group.locked && (
<HStack alignItems='center' space={4}>
<label className='ml-auto cursor-pointer' htmlFor='group-timeline-visible'>
<Text theme='muted'>
<FormattedMessage id='compose_group.share_to_followers' defaultMessage='Share with my followers' />
</Text>
</label>
<Toggle
id='group-timeline-visible'
checked={groupTimelineVisible}
onChange={handleToggleChange}
size='sm'
/>
</HStack>
)}
/> />
</HStack> </HStack>
</div> </div>
@ -69,7 +89,7 @@ const GroupTimeline: React.FC<IGroupTimeline> = (props) => {
<Timeline <Timeline
scrollKey='group_timeline' scrollKey='group_timeline'
timelineId={`group:${groupId}`} timelineId={composeId}
onLoadMore={handleLoadMore} onLoadMore={handleLoadMore}
emptyMessage={ emptyMessage={
<Stack space={4} className='py-6' justifyContent='center' alignItems='center'> <Stack space={4} className='py-6' justifyContent='center' alignItems='center'>

View file

@ -27,7 +27,7 @@ const GroupGridItem = forwardRef((props: IGroup, ref: React.ForwardedRef<HTMLDiv
> >
<Link to={`/groups/${group.id}`}> <Link to={`/groups/${group.id}`}>
<Stack <Stack
className='aspect-w-10 aspect-h-7 h-full w-full overflow-hidden rounded-lg' className='aspect-h-7 aspect-w-10 h-full w-full overflow-hidden rounded-lg'
ref={ref} ref={ref}
style={{ minHeight: 180 }} style={{ minHeight: 180 }}
> >
@ -62,7 +62,7 @@ const GroupGridItem = forwardRef((props: IGroup, ref: React.ForwardedRef<HTMLDiv
</Stack> </Stack>
<div <div
className='absolute inset-x-0 bottom-0 z-0 flex justify-center rounded-b-lg bg-gradient-to-t from-gray-900 to-transparent pt-12 pb-8 transition-opacity duration-500' className='absolute inset-x-0 bottom-0 z-0 flex justify-center rounded-b-lg bg-gradient-to-t from-gray-900 to-transparent pb-8 pt-12 transition-opacity duration-500'
/> />
</Stack> </Stack>
</Link> </Link>

View file

@ -18,7 +18,6 @@ export default () => {
const { groups, isLoading } = usePendingGroups(); const { groups, isLoading } = usePendingGroups();
const renderBlankslate = () => ( const renderBlankslate = () => (
<Stack <Stack
space={4} space={4}

View file

@ -24,7 +24,7 @@ const SuggestedAccountsStep = ({ onNext }: { onNext: () => void }) => {
} }
return ( return (
<div className='flex flex-col sm:pt-4 sm:pb-10'> <div className='flex flex-col sm:pb-10 sm:pt-4'>
<ScrollableList <ScrollableList
isLoading={isFetching} isLoading={isFetching}
scrollKey='suggestions' scrollKey='suggestions'

View file

@ -14,7 +14,7 @@ const PlaceholderEventPreview = () => {
<Stack className='p-2.5' space={2}> <Stack className='p-2.5' space={2}>
<Text weight='semibold'>{generateText(eventNameLength)}</Text> <Text weight='semibold'>{generateText(eventNameLength)}</Text>
<div className='flex flex-wrap gap-y-1 gap-x-2 text-gray-700 dark:text-gray-600'> <div className='flex flex-wrap gap-x-2 gap-y-1 text-gray-700 dark:text-gray-600'>
<span>{generateText(nameLength)}</span> <span>{generateText(nameLength)}</span>
<span>{generateText(nameLength)}</span> <span>{generateText(nameLength)}</span>
<span>{generateText(nameLength)}</span> <span>{generateText(nameLength)}</span>

View file

@ -14,7 +14,7 @@ const PlaceholderGroupCard = () => {
<div className='relative grow basis-1/2 rounded-t-lg bg-gray-300 dark:bg-gray-800' /> <div className='relative grow basis-1/2 rounded-t-lg bg-gray-300 dark:bg-gray-800' />
{/* Group Avatar */} {/* Group Avatar */}
<div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'> <div className='absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2'>
<div className='h-16 w-16 rounded-full bg-gray-500 ring-2 ring-white dark:bg-primary-800 dark:ring-primary-900' /> <div className='h-16 w-16 rounded-full bg-gray-500 ring-2 ring-white dark:bg-primary-800 dark:ring-primary-900' />
</div> </div>

View file

@ -9,11 +9,11 @@ const PlaceholderGroupDiscover = () => {
return ( return (
<Stack space={2} className='animate-pulse'> <Stack space={2} className='animate-pulse'>
<Stack className='aspect-w-10 aspect-h-7 h-full w-full overflow-hidden rounded-lg'> <Stack className='aspect-h-7 aspect-w-10 h-full w-full overflow-hidden rounded-lg'>
{/* Group Cover Image */} {/* Group Cover Image */}
<div className='absolute inset-0 rounded-t-lg bg-gray-300 object-cover dark:bg-gray-800' /> <div className='absolute inset-0 rounded-t-lg bg-gray-300 object-cover dark:bg-gray-800' />
<Stack justifyContent='end' className='z-10 p-4 text-white' space={3}> <Stack justifyContent='end' className='z-10 p-4 text-gray-900 dark:text-gray-100' space={3}>
{/* Group Avatar */} {/* Group Avatar */}
<div className='h-11 w-11 rounded-full bg-gray-500 dark:bg-gray-700 dark:ring-primary-900' /> <div className='h-11 w-11 rounded-full bg-gray-500 dark:bg-gray-700 dark:ring-primary-900' />

View file

@ -21,7 +21,7 @@ const Footer = () => {
}); });
return ( return (
<footer className='relative mx-auto mt-auto max-w-7xl py-12 px-4 sm:px-6 lg:px-8 xl:flex xl:items-center xl:justify-between'> <footer className='relative mx-auto mt-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8 xl:flex xl:items-center xl:justify-between'>
<div className='flex flex-wrap justify-center'> <div className='flex flex-wrap justify-center'>
{navlinks.map((link, idx) => { {navlinks.map((link, idx) => {
const url = link.get('url'); const url = link.get('url');

View file

@ -69,7 +69,7 @@ const Header = () => {
<nav className='mx-auto max-w-7xl px-4 sm:px-6 lg:px-8' aria-label='Header'> <nav className='mx-auto max-w-7xl px-4 sm:px-6 lg:px-8' aria-label='Header'>
<div className='flex w-full items-center justify-between border-b border-indigo-500 py-6 lg:border-none'> <div className='flex w-full items-center justify-between border-b border-indigo-500 py-6 lg:border-none'>
<div className='relative flex w-36 items-center sm:justify-center'> <div className='relative flex w-36 items-center sm:justify-center'>
<div className='absolute -top-24 -left-6 z-0 hidden md:block'> <div className='absolute -left-6 -top-24 z-0 hidden md:block'>
<Sonar /> <Sonar />
</div> </div>

View file

@ -3,12 +3,12 @@ import React from 'react';
const Sonar = () => ( const Sonar = () => (
<div className='relative'> <div className='relative'>
<div className='relative h-48 w-48'> <div className='relative h-48 w-48'>
<div className='pointer-events-none absolute top-0 left-0 h-full w-full animate-sonar-scale-4 rounded-full bg-primary-600/25 opacity-0 dark:bg-primary-600/25' /> <div className='pointer-events-none absolute left-0 top-0 h-full w-full animate-sonar-scale-4 rounded-full bg-primary-600/25 opacity-0 dark:bg-primary-600/25' />
<div className='pointer-events-none absolute top-0 left-0 h-full w-full animate-sonar-scale-3 rounded-full bg-primary-600/25 opacity-0 dark:bg-primary-600/25' /> <div className='pointer-events-none absolute left-0 top-0 h-full w-full animate-sonar-scale-3 rounded-full bg-primary-600/25 opacity-0 dark:bg-primary-600/25' />
<div className='pointer-events-none absolute top-0 left-0 h-full w-full animate-sonar-scale-2 rounded-full bg-primary-600/25 opacity-0 dark:bg-primary-600/25' /> <div className='pointer-events-none absolute left-0 top-0 h-full w-full animate-sonar-scale-2 rounded-full bg-primary-600/25 opacity-0 dark:bg-primary-600/25' />
<div className='pointer-events-none absolute top-0 left-0 h-full w-full animate-sonar-scale-1 rounded-full bg-primary-600/25 opacity-0 dark:bg-primary-600/25' /> <div className='pointer-events-none absolute left-0 top-0 h-full w-full animate-sonar-scale-1 rounded-full bg-primary-600/25 opacity-0 dark:bg-primary-600/25' />
<div className='absolute top-0 left-0 h-48 w-48 rounded-full bg-white dark:bg-primary-900' /> <div className='absolute left-0 top-0 h-48 w-48 rounded-full bg-white dark:bg-primary-900' />
</div> </div>
</div> </div>
); );

View file

@ -1,9 +1,9 @@
import noop from 'lodash/noop'; import noop from 'lodash/noop';
import React from 'react'; import React from 'react';
import Toggle from 'react-toggle';
import { toggleStatusReport } from 'soapbox/actions/reports'; import { toggleStatusReport } from 'soapbox/actions/reports';
import StatusContent from 'soapbox/components/status-content'; import StatusContent from 'soapbox/components/status-content';
import { Toggle } from 'soapbox/components/ui';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import Bundle from '../../ui/components/bundle'; import Bundle from '../../ui/components/bundle';
@ -88,7 +88,7 @@ const StatusCheckBox: React.FC<IStatusCheckBox> = ({ id, disabled }) => {
</div> </div>
<div className='status-check-box-toggle'> <div className='status-check-box-toggle'>
<Toggle checked={checked} onChange={onToggle} disabled={disabled} icons={false} /> <Toggle checked={checked} onChange={onToggle} disabled={disabled} />
</div> </div>
</div> </div>
); );

View file

@ -1,6 +1,5 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import Toggle from 'react-toggle';
import { import {
changeEditEventApprovalRequired, changeEditEventApprovalRequired,
@ -22,7 +21,7 @@ import { closeModal, openModal } from 'soapbox/actions/modals';
import { ADDRESS_ICONS } from 'soapbox/components/autosuggest-location'; import { ADDRESS_ICONS } from 'soapbox/components/autosuggest-location';
import LocationSearch from 'soapbox/components/location-search'; import LocationSearch from 'soapbox/components/location-search';
import { checkEventComposeContent } from 'soapbox/components/modal-root'; import { checkEventComposeContent } from 'soapbox/components/modal-root';
import { Button, Form, FormGroup, HStack, Icon, IconButton, Input, Modal, Spinner, Stack, Tabs, Text, Textarea } from 'soapbox/components/ui'; import { Button, Form, FormGroup, HStack, Icon, IconButton, Input, Modal, Spinner, Stack, Tabs, Text, Textarea, Toggle } from 'soapbox/components/ui';
import AccountContainer from 'soapbox/containers/account-container'; import AccountContainer from 'soapbox/containers/account-container';
import { isCurrentOrFutureDate } from 'soapbox/features/compose/components/schedule-form'; import { isCurrentOrFutureDate } from 'soapbox/features/compose/components/schedule-form';
import BundleContainer from 'soapbox/features/ui/containers/bundle-container'; import BundleContainer from 'soapbox/features/ui/containers/bundle-container';
@ -218,7 +217,7 @@ const ComposeEventModal: React.FC<IComposeEventModal> = ({ onClose }) => {
{banner ? ( {banner ? (
<> <>
<img className='h-full w-full object-cover' src={banner.url} alt='' /> <img className='h-full w-full object-cover' src={banner.url} alt='' />
<IconButton className='absolute top-2 right-2' src={require('@tabler/icons/x.svg')} onClick={handleClearBanner} /> <IconButton className='absolute right-2 top-2' src={require('@tabler/icons/x.svg')} onClick={handleClearBanner} />
</> </>
) : ( ) : (
<UploadButton disabled={isUploading} onSelectFile={handleFiles} /> <UploadButton disabled={isUploading} onSelectFile={handleFiles} />
@ -273,7 +272,6 @@ const ComposeEventModal: React.FC<IComposeEventModal> = ({ onClose }) => {
</FormGroup> </FormGroup>
<HStack alignItems='center' space={2}> <HStack alignItems='center' space={2}>
<Toggle <Toggle
icons={false}
checked={!!endTime} checked={!!endTime}
onChange={onChangeHasEndTime} onChange={onChangeHasEndTime}
/> />
@ -302,7 +300,6 @@ const ComposeEventModal: React.FC<IComposeEventModal> = ({ onClose }) => {
{!id && ( {!id && (
<HStack alignItems='center' space={2}> <HStack alignItems='center' space={2}>
<Toggle <Toggle
icons={false}
checked={approvalRequired} checked={approvalRequired}
onChange={onChangeApprovalRequired} onChange={onChangeApprovalRequired}
/> />

View file

@ -98,7 +98,6 @@ const EditAnnouncementModal: React.FC<IEditAnnouncementModal> = ({ onClose }) =>
</FormGroup> </FormGroup>
<HStack alignItems='center' space={2}> <HStack alignItems='center' space={2}>
<Toggle <Toggle
icons={false}
checked={allDay} checked={allDay}
onChange={onChangeAllDay} onChange={onChangeAllDay}
/> />

View file

@ -1,11 +1,10 @@
import { Map as ImmutableMap } from 'immutable'; import { Map as ImmutableMap } from 'immutable';
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import Toggle from 'react-toggle';
import { updateMrf } from 'soapbox/actions/mrf'; import { updateMrf } from 'soapbox/actions/mrf';
import List, { ListItem } from 'soapbox/components/list'; import List, { ListItem } from 'soapbox/components/list';
import { Modal } from 'soapbox/components/ui'; import { Modal, Toggle } from 'soapbox/components/ui';
import { useAppSelector, useAppDispatch } from 'soapbox/hooks'; import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
import { makeGetRemoteInstance } from 'soapbox/selectors'; import { makeGetRemoteInstance } from 'soapbox/selectors';
import toast from 'soapbox/toast'; import toast from 'soapbox/toast';
@ -86,7 +85,6 @@ const EditFederationModal: React.FC<IEditFederationModal> = ({ host, onClose })
<Toggle <Toggle
checked={reject} checked={reject}
onChange={handleDataChange('reject')} onChange={handleDataChange('reject')}
icons={false}
id='reject' id='reject'
/> />
</ListItem> </ListItem>
@ -95,7 +93,6 @@ const EditFederationModal: React.FC<IEditFederationModal> = ({ host, onClose })
<Toggle <Toggle
checked={fullMediaRemoval} checked={fullMediaRemoval}
onChange={handleMediaRemoval} onChange={handleMediaRemoval}
icons={false}
id='media_removal' id='media_removal'
disabled={reject} disabled={reject}
/> />
@ -105,7 +102,6 @@ const EditFederationModal: React.FC<IEditFederationModal> = ({ host, onClose })
<Toggle <Toggle
checked={media_nsfw} checked={media_nsfw}
onChange={handleDataChange('media_nsfw')} onChange={handleDataChange('media_nsfw')}
icons={false}
id='media_nsfw' id='media_nsfw'
disabled={reject || media_removal} disabled={reject || media_removal}
/> />
@ -115,7 +111,6 @@ const EditFederationModal: React.FC<IEditFederationModal> = ({ host, onClose })
<Toggle <Toggle
checked={followers_only} checked={followers_only}
onChange={handleDataChange('followers_only')} onChange={handleDataChange('followers_only')}
icons={false}
id='followers_only' id='followers_only'
disabled={reject} disabled={reject}
/> />
@ -125,7 +120,6 @@ const EditFederationModal: React.FC<IEditFederationModal> = ({ host, onClose })
<Toggle <Toggle
checked={federated_timeline_removal} checked={federated_timeline_removal}
onChange={handleDataChange('federated_timeline_removal')} onChange={handleDataChange('federated_timeline_removal')}
icons={false}
id='federated_timeline_removal' id='federated_timeline_removal'
disabled={reject || followers_only} disabled={reject || followers_only}
/> />

View file

@ -3,8 +3,10 @@ import { FormattedMessage } from 'react-intl';
import { Avatar, Divider, HStack, Stack, Text, Button } from 'soapbox/components/ui'; import { Avatar, Divider, HStack, Stack, Text, Button } from 'soapbox/components/ui';
import type { Group } from 'soapbox/schemas';
interface IConfirmationStep { interface IConfirmationStep {
group: any group: Group
} }
const ConfirmationStep: React.FC<IConfirmationStep> = ({ group }) => { const ConfirmationStep: React.FC<IConfirmationStep> = ({ group }) => {
@ -53,24 +55,30 @@ const ConfirmationStep: React.FC<IConfirmationStep> = ({ group }) => {
<Stack space={5}> <Stack space={5}>
<InfoListItem number={1}> <InfoListItem number={1}>
<FormattedMessage <Text theme='muted'>
id='manage_group.confirmation.info_1' <FormattedMessage
defaultMessage='As the owner of this group, you can assign staff, delete posts and much more.' id='manage_group.confirmation.info_1'
/> defaultMessage='As the owner of this group, you can assign staff, delete posts and much more.'
/>
</Text>
</InfoListItem> </InfoListItem>
<InfoListItem number={2}> <InfoListItem number={2}>
<FormattedMessage <Text theme='muted'>
id='manage_group.confirmation.info_2' <FormattedMessage
defaultMessage="Post the group's first post and get the conversation started." id='manage_group.confirmation.info_2'
/> defaultMessage="Post the group's first post and get the conversation started."
/>
</Text>
</InfoListItem> </InfoListItem>
<InfoListItem number={3}> <InfoListItem number={3}>
<FormattedMessage <Text theme='muted'>
id='manage_group.confirmation.info_3' <FormattedMessage
defaultMessage='Share your new group with friends, family and followers to grow its membership.' id='manage_group.confirmation.info_3'
/> defaultMessage='Share your new group with friends, family and followers to grow its membership.'
/>
</Text>
</InfoListItem> </InfoListItem>
</Stack> </Stack>
</Stack> </Stack>
@ -96,7 +104,7 @@ interface IInfoListNumber {
const InfoListNumber: React.FC<IInfoListNumber> = ({ number }) => { const InfoListNumber: React.FC<IInfoListNumber> = ({ number }) => {
return ( return (
<div className='flex h-7 w-7 items-center justify-center rounded-full border border-gray-200'> <div className='flex h-7 w-7 shrink-0 items-center justify-center rounded-full border border-gray-200 dark:border-gray-800'>
<Text theme='primary' size='sm' weight='bold'>{number}</Text> <Text theme='primary' size='sm' weight='bold'>{number}</Text>
</div> </div>
); );
@ -109,9 +117,11 @@ interface IInfoListItem {
const InfoListItem: React.FC<IInfoListItem> = ({ number, children }) => { const InfoListItem: React.FC<IInfoListItem> = ({ number, children }) => {
return ( return (
<HStack space={3}> <HStack alignItems='top' space={3}>
<div><InfoListNumber number={number} /></div> <InfoListNumber number={number} />
<div>{children}</div> <div className='mt-0.5'>
{children}
</div>
</HStack> </HStack>
); );
}; };

View file

@ -65,7 +65,7 @@ const HeaderPicker: React.FC<IMediaInput> = ({ src, onChange, accept, disabled }
const AvatarPicker: React.FC<IMediaInput> = ({ src, onChange, accept, disabled }) => { const AvatarPicker: React.FC<IMediaInput> = ({ src, onChange, accept, disabled }) => {
return ( return (
<label className='absolute left-1/2 bottom-0 h-20 w-20 -translate-x-1/2 translate-y-1/2 cursor-pointer rounded-full bg-primary-500 ring-4 ring-white dark:ring-primary-900'> <label className='absolute bottom-0 left-1/2 h-20 w-20 -translate-x-1/2 translate-y-1/2 cursor-pointer rounded-full bg-primary-500 ring-4 ring-white dark:ring-primary-900'>
{src && <Avatar src={src} size={80} />} {src && <Avatar src={src} size={80} />}
<HStack <HStack
alignItems='center' alignItems='center'

View file

@ -1,11 +1,10 @@
import React from 'react'; import React from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import Toggle from 'react-toggle';
import { muteAccount } from 'soapbox/actions/accounts'; import { muteAccount } from 'soapbox/actions/accounts';
import { closeModal } from 'soapbox/actions/modals'; import { closeModal } from 'soapbox/actions/modals';
import { toggleHideNotifications, changeMuteDuration } from 'soapbox/actions/mutes'; import { toggleHideNotifications, changeMuteDuration } from 'soapbox/actions/mutes';
import { Modal, HStack, Stack, Text } from 'soapbox/components/ui'; import { Modal, HStack, Stack, Text, Toggle } from 'soapbox/components/ui';
import DurationSelector from 'soapbox/features/compose/components/polls/duration-selector'; import DurationSelector from 'soapbox/features/compose/components/polls/duration-selector';
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
import { makeGetAccount } from 'soapbox/selectors'; import { makeGetAccount } from 'soapbox/selectors';
@ -74,7 +73,6 @@ const MuteModal = () => {
<Toggle <Toggle
checked={notifications} checked={notifications}
onChange={toggleNotifications} onChange={toggleNotifications}
icons={false}
/> />
</HStack> </HStack>
</label> </label>
@ -90,7 +88,6 @@ const MuteModal = () => {
<Toggle <Toggle
checked={duration !== 0} checked={duration !== 0}
onChange={toggleAutoExpire} onChange={toggleAutoExpire}
icons={false}
/> />
</HStack> </HStack>
</label> </label>

View file

@ -1,11 +1,10 @@
import { OrderedSet } from 'immutable'; import { OrderedSet } from 'immutable';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import Toggle from 'react-toggle';
import { changeReportBlock, changeReportForward } from 'soapbox/actions/reports'; import { changeReportBlock, changeReportForward } from 'soapbox/actions/reports';
import { fetchRules } from 'soapbox/actions/rules'; import { fetchRules } from 'soapbox/actions/rules';
import { Button, FormGroup, HStack, Stack, Text } from 'soapbox/components/ui'; import { Button, FormGroup, HStack, Stack, Text, Toggle } from 'soapbox/components/ui';
import StatusCheckBox from 'soapbox/features/report/components/status-check-box'; import StatusCheckBox from 'soapbox/features/report/components/status-check-box';
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
import { isRemote, getDomain } from 'soapbox/utils/accounts'; import { isRemote, getDomain } from 'soapbox/utils/accounts';
@ -101,7 +100,6 @@ const OtherActionsStep = ({ account }: IOtherActionsStep) => {
<Toggle <Toggle
checked={isBlocked} checked={isBlocked}
onChange={handleBlockChange} onChange={handleBlockChange}
icons={false}
id='report-block' id='report-block'
/> />
@ -119,7 +117,6 @@ const OtherActionsStep = ({ account }: IOtherActionsStep) => {
<Toggle <Toggle
checked={isForward} checked={isForward}
onChange={handleForwardChange} onChange={handleForwardChange}
icons={false}
id='report-forward' id='report-forward'
disabled={isSubmitting} disabled={isSubmitting}
/> />

View file

@ -159,7 +159,7 @@ const ReasonStep = (_props: IReasonStep) => {
})} })}
/> />
<div <div
className={clsx('pointer-events-none absolute inset-x-0 bottom-0 flex justify-center rounded-b-lg bg-gradient-to-t from-white pt-12 pb-8 transition-opacity duration-500 dark:from-gray-900', { className={clsx('pointer-events-none absolute inset-x-0 bottom-0 flex justify-center rounded-b-lg bg-gradient-to-t from-white pb-8 pt-12 transition-opacity duration-500 dark:from-gray-900', {
'opacity-0': isNearBottom, 'opacity-0': isNearBottom,
'opacity-100': !isNearBottom, 'opacity-100': !isNearBottom,
})} })}

View file

@ -67,7 +67,7 @@ const Navbar = () => {
<div className='mx-auto max-w-7xl px-2 sm:px-6 lg:px-8'> <div className='mx-auto max-w-7xl px-2 sm:px-6 lg:px-8'>
<div className='relative flex h-12 justify-between lg:h-16'> <div className='relative flex h-12 justify-between lg:h-16'>
{account && ( {account && (
<div className='absolute inset-y-0 left-0 flex items-center rtl:right-0 rtl:left-auto lg:hidden'> <div className='absolute inset-y-0 left-0 flex items-center rtl:left-auto rtl:right-0 lg:hidden'>
<button onClick={onOpenSidebar}> <button onClick={onOpenSidebar}>
<Avatar src={account.avatar} size={34} /> <Avatar src={account.avatar} size={34} />
</button> </button>

View file

@ -20,7 +20,7 @@ const PromoPanel: React.FC = () => {
{promoItems.map((item, i) => ( {promoItems.map((item, i) => (
<Text key={i}> <Text key={i}>
<a className='flex items-center' href={item.url} target='_blank'> <a className='flex items-center' href={item.url} target='_blank'>
<ForkAwesomeIcon id={item.icon} className='mr-2 flex-none text-lg rtl:mr-0 rtl:ml-2' fixedWidth /> <ForkAwesomeIcon id={item.icon} className='mr-2 flex-none text-lg rtl:ml-2 rtl:mr-0' fixedWidth />
{item.textLocales.get(locale) || item.text} {item.textLocales.get(locale) || item.text}
</a> </a>
</Text> </Text>

View file

@ -551,19 +551,19 @@ export function Groups() {
} }
export function GroupsDiscover() { export function GroupsDiscover() {
return import(/* webpackChunkName: "features/groups/discover" */'../../groups/discover'); return import(/* webpackChunkName: "features/groups" */'../../groups/discover');
} }
export function GroupsPopular() { export function GroupsPopular() {
return import(/* webpackChunkName: "features/groups/discover" */'../../groups/popular'); return import(/* webpackChunkName: "features/groups" */'../../groups/popular');
} }
export function GroupsSuggested() { export function GroupsSuggested() {
return import(/* webpackChunkName: "features/groups/discover" */'../../groups/suggested'); return import(/* webpackChunkName: "features/groups" */'../../groups/suggested');
} }
export function PendingGroupRequests() { export function PendingGroupRequests() {
return import(/* webpackChunkName: "features/groups/discover" */'../../groups/pending-requests'); return import(/* webpackChunkName: "features/groups" */'../../groups/pending-requests');
} }
export function GroupMembers() { export function GroupMembers() {

View file

@ -441,6 +441,7 @@
"compose_form.spoiler_placeholder": "Write your warning here (optional)", "compose_form.spoiler_placeholder": "Write your warning here (optional)",
"compose_form.spoiler_remove": "Remove sensitive", "compose_form.spoiler_remove": "Remove sensitive",
"compose_form.spoiler_title": "Sensitive content", "compose_form.spoiler_title": "Sensitive content",
"compose_group.share_to_followers": "Share with my followers",
"confirmation_modal.cancel": "Cancel", "confirmation_modal.cancel": "Cancel",
"confirmations.admin.deactivate_user.confirm": "Deactivate @{name}", "confirmations.admin.deactivate_user.confirm": "Deactivate @{name}",
"confirmations.admin.deactivate_user.heading": "Deactivate @{acct}", "confirmations.admin.deactivate_user.heading": "Deactivate @{acct}",
@ -797,8 +798,9 @@
"group.promote.admin.confirmation.message": "Are you sure you want to assign the admin role to @{name}?", "group.promote.admin.confirmation.message": "Are you sure you want to assign the admin role to @{name}?",
"group.promote.admin.confirmation.title": "Assign Admin Role", "group.promote.admin.confirmation.title": "Assign Admin Role",
"group.promote.admin.success": "@{name} is now an admin", "group.promote.admin.success": "@{name} is now an admin",
"group.report.label": "Report",
"group.role.admin": "Admin", "group.role.admin": "Admin",
"group.role.moderator": "Moderator", "group.role.owner": "Owner",
"group.tabs.all": "All", "group.tabs.all": "All",
"group.tabs.members": "Members", "group.tabs.members": "Members",
"group.upload_banner": "Upload photo", "group.upload_banner": "Upload photo",

View file

@ -52,6 +52,7 @@ import {
COMPOSE_SET_STATUS, COMPOSE_SET_STATUS,
COMPOSE_EVENT_REPLY, COMPOSE_EVENT_REPLY,
COMPOSE_EDITOR_STATE_SET, COMPOSE_EDITOR_STATE_SET,
COMPOSE_SET_GROUP_TIMELINE_VISIBLE,
} from '../actions/compose'; } from '../actions/compose';
import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS } from '../actions/me'; import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS } from '../actions/me';
import { SETTING_CHANGE, FE_NAME } from '../actions/settings'; import { SETTING_CHANGE, FE_NAME } from '../actions/settings';
@ -105,6 +106,7 @@ export const ReducerCompose = ImmutableRecord({
tagHistory: ImmutableList<string>(), tagHistory: ImmutableList<string>(),
text: '', text: '',
to: ImmutableOrderedSet<string>(), to: ImmutableOrderedSet<string>(),
group_timeline_visible: false, // TruthSocial
}); });
type State = ImmutableMap<string, Compose>; type State = ImmutableMap<string, Compose>;
@ -495,6 +497,8 @@ export default function compose(state = initialState, action: AnyAction) {
return updateCompose(state, action.id, compose => compose.update('to', mentions => mentions!.add(action.account))); return updateCompose(state, action.id, compose => compose.update('to', mentions => mentions!.add(action.account)));
case COMPOSE_REMOVE_FROM_MENTIONS: case COMPOSE_REMOVE_FROM_MENTIONS:
return updateCompose(state, action.id, compose => compose.update('to', mentions => mentions!.delete(action.account))); return updateCompose(state, action.id, compose => compose.update('to', mentions => mentions!.delete(action.account)));
case COMPOSE_SET_GROUP_TIMELINE_VISIBLE:
return updateCompose(state, action.id, compose => compose.set('group_timeline_visible', action.groupTimelineVisible));
case ME_FETCH_SUCCESS: case ME_FETCH_SUCCESS:
return updateCompose(state, 'default', compose => importAccount(compose, action.me)); return updateCompose(state, 'default', compose => importAccount(compose, action.me));
case ME_PATCH_SUCCESS: case ME_PATCH_SUCCESS:

View file

@ -21,7 +21,6 @@
@import 'components/display-name'; @import 'components/display-name';
@import 'components/columns'; @import 'components/columns';
@import 'components/search'; @import 'components/search';
@import 'components/react-toggle';
@import 'components/video-player'; @import 'components/video-player';
@import 'components/audio-player'; @import 'components/audio-player';
@import 'components/crypto-donate'; @import 'components/crypto-donate';

View file

@ -1,96 +0,0 @@
.react-toggle {
display: inline-block;
position: relative;
cursor: pointer;
background-color: transparent;
border: 0;
padding: 0;
user-select: none;
-webkit-tap-highlight-color: #0000;
-webkit-tap-highlight-color: transparent;
&:focus-within .react-toggle-track {
@apply ring-2 ring-offset-2 ring-primary-500;
}
}
.react-toggle-screenreader-only {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.react-toggle--disabled {
cursor: not-allowed;
opacity: 0.5;
transition: opacity 0.25s;
}
.react-toggle-track {
@apply bg-gray-500 dark:bg-gray-700 h-[30px] w-[50px] p-0 rounded-full transition-colors;
}
.react-toggle--checked .react-toggle-track {
@apply bg-primary-600 dark:bg-accent-blue;
}
.react-toggle-track-check {
position: absolute;
width: 14px;
height: 10px;
top: 0;
bottom: 0;
margin-top: auto;
margin-bottom: auto;
line-height: 0;
left: 8px;
opacity: 0;
transition: opacity 0.25s ease;
}
.react-toggle--checked .react-toggle-track-check {
opacity: 1;
transition: opacity 0.25s ease;
}
.react-toggle-track-x {
position: absolute;
width: 10px;
height: 10px;
top: 0;
bottom: 0;
margin-top: auto;
margin-bottom: auto;
line-height: 0;
right: 10px;
opacity: 1;
transition: opacity 0.25s ease;
}
.react-toggle--checked .react-toggle-track-x {
opacity: 0;
}
.react-toggle-thumb {
position: absolute;
top: 1px;
left: 1px;
width: 28px;
height: 28px;
border: 1px solid #fff;
border-radius: 50%;
background-color: #fff;
box-sizing: border-box;
transition: all 0.25s ease;
transition-property: border-color, left;
}
.react-toggle--checked .react-toggle-thumb {
@apply border-primary-600 dark:border-accent-blue;
left: 21px;
}

View file

@ -77,8 +77,8 @@
"@sentry/tracing": "^7.37.2", "@sentry/tracing": "^7.37.2",
"@tabler/icons": "^2.0.0", "@tabler/icons": "^2.0.0",
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.3",
"@tailwindcss/line-clamp": "^0.4.2", "@tailwindcss/line-clamp": "^0.4.4",
"@tailwindcss/typography": "^0.5.7", "@tailwindcss/typography": "^0.5.9",
"@tanstack/react-query": "^4.0.10", "@tanstack/react-query": "^4.0.10",
"@testing-library/react": "^14.0.0", "@testing-library/react": "^14.0.0",
"@types/escape-html": "^1.0.1", "@types/escape-html": "^1.0.1",
@ -96,7 +96,6 @@
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"@types/react-sparklines": "^1.7.2", "@types/react-sparklines": "^1.7.2",
"@types/react-swipeable-views": "^0.13.1", "@types/react-swipeable-views": "^0.13.1",
"@types/react-toggle": "^4.0.3",
"@types/redux-mock-store": "^1.0.3", "@types/redux-mock-store": "^1.0.3",
"@types/seedrandom": "^3.0.2", "@types/seedrandom": "^3.0.2",
"@types/semver": "^7.3.9", "@types/semver": "^7.3.9",
@ -175,7 +174,6 @@
"react-sticky-box": "^2.0.0", "react-sticky-box": "^2.0.0",
"react-swipeable-views": "^0.14.0", "react-swipeable-views": "^0.14.0",
"react-textarea-autosize": "^8.3.4", "react-textarea-autosize": "^8.3.4",
"react-toggle": "^4.1.2",
"react-virtuoso": "^4.0.8", "react-virtuoso": "^4.0.8",
"redux": "^4.1.1", "redux": "^4.1.1",
"redux-immutable": "^4.0.0", "redux-immutable": "^4.0.0",
@ -237,7 +235,7 @@
"eslint-plugin-promise": "^6.0.0", "eslint-plugin-promise": "^6.0.0",
"eslint-plugin-react": "^7.25.1", "eslint-plugin-react": "^7.25.1",
"eslint-plugin-react-hooks": "^4.2.0", "eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-tailwindcss": "^3.8.3", "eslint-plugin-tailwindcss": "^3.10.1",
"fake-indexeddb": "^4.0.0", "fake-indexeddb": "^4.0.0",
"husky": "^8.0.0", "husky": "^8.0.0",
"jest": "^29.0.0", "jest": "^29.0.0",
@ -250,7 +248,7 @@
"storybook-react-intl": "^1.1.1", "storybook-react-intl": "^1.1.1",
"stylelint": "^14.0.0", "stylelint": "^14.0.0",
"stylelint-config-standard-scss": "^6.1.0", "stylelint-config-standard-scss": "^6.1.0",
"tailwindcss": "^3.2.1", "tailwindcss": "^3.3.1",
"ts-jest": "^29.0.0", "ts-jest": "^29.0.0",
"webpack-dev-server": "^4.9.1", "webpack-dev-server": "^4.9.1",
"yargs": "^17.6.2" "yargs": "^17.6.2"

157
yarn.lock
View file

@ -3998,15 +3998,15 @@
dependencies: dependencies:
mini-svg-data-uri "^1.2.3" mini-svg-data-uri "^1.2.3"
"@tailwindcss/line-clamp@^0.4.2": "@tailwindcss/line-clamp@^0.4.4":
version "0.4.2" version "0.4.4"
resolved "https://registry.yarnpkg.com/@tailwindcss/line-clamp/-/line-clamp-0.4.2.tgz#f353c5a8ab2c939c6267ac5b907f012e5ee130f9" resolved "https://registry.yarnpkg.com/@tailwindcss/line-clamp/-/line-clamp-0.4.4.tgz#767cf8e5d528a5d90c9740ca66eb079f5e87d423"
integrity sha512-HFzAQuqYCjyy/SX9sLGB1lroPzmcnWv1FHkIpmypte10hptf4oPUfucryMKovZh2u0uiS9U5Ty3GghWfEJGwVw== integrity sha512-5U6SY5z8N42VtrCrKlsTAA35gy2VSyYtHWCsg1H87NU1SXnEfekTVlrga9fzUDrrHcGi2Lb5KenUWb4lRQT5/g==
"@tailwindcss/typography@^0.5.7": "@tailwindcss/typography@^0.5.9":
version "0.5.7" version "0.5.9"
resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.7.tgz#e0b95bea787ee14c5a34a74fc824e6fe86ea8855" resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.9.tgz#027e4b0674929daaf7c921c900beee80dbad93e8"
integrity sha512-JTTSTrgZfp6Ki4svhPA4mkd9nmQ/j9EfE7SbHJ1cLtthKkpW2OxsFXzSmxbhYbEkfNIyAyhle5p4SYyKRbz/jg== integrity sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==
dependencies: dependencies:
lodash.castarray "^4.4.0" lodash.castarray "^4.4.0"
lodash.isplainobject "^4.0.6" lodash.isplainobject "^4.0.6"
@ -4642,13 +4642,6 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react-toggle@^4.0.3":
version "4.0.3"
resolved "https://registry.yarnpkg.com/@types/react-toggle/-/react-toggle-4.0.3.tgz#8db98ac8d2c5e8c03c2d3a42027555c1cd2289da"
integrity sha512-57QdMWeeQdRjM2/p+udgYerxUbSkmeUIW18kwUttcci6GHkgxoqCsDZfRtsCsAHcvvM5VBQdtDUEgLWo2e87mA==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@17", "@types/react@^18.0.26": "@types/react@*", "@types/react@17", "@types/react@^18.0.26":
version "18.0.26" version "18.0.26"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.26.tgz#8ad59fc01fef8eaf5c74f4ea392621749f0b7917" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.26.tgz#8ad59fc01fef8eaf5c74f4ea392621749f0b7917"
@ -5290,16 +5283,7 @@ acorn-jsx@^5.3.1, acorn-jsx@^5.3.2:
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
acorn-node@^1.8.2: acorn-walk@^7.2.0:
version "1.8.2"
resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8"
integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==
dependencies:
acorn "^7.0.0"
acorn-walk "^7.0.0"
xtend "^4.0.2"
acorn-walk@^7.0.0, acorn-walk@^7.2.0:
version "7.2.0" version "7.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
@ -5314,7 +5298,7 @@ acorn@^6.4.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6"
integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==
acorn@^7.0.0, acorn@^7.4.1: acorn@^7.4.1:
version "7.4.1" version "7.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
@ -5508,6 +5492,11 @@ ansi-to-html@^0.6.11:
dependencies: dependencies:
entities "^2.0.0" entities "^2.0.0"
any-promise@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==
anymatch@^2.0.0: anymatch@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
@ -7025,7 +7014,7 @@ commander@^2.18.0, commander@^2.19.0, commander@^2.20.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commander@^4.1.1: commander@^4.0.0, commander@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
@ -7761,11 +7750,6 @@ define-property@^2.0.2:
is-descriptor "^1.0.2" is-descriptor "^1.0.2"
isobject "^3.0.1" isobject "^3.0.1"
defined@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=
delay@^5.0.0: delay@^5.0.0:
version "5.0.0" version "5.0.0"
resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d"
@ -7858,15 +7842,6 @@ detect-port@^1.3.0:
address "^1.0.1" address "^1.0.1"
debug "4" debug "4"
detective@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.1.tgz#6af01eeda11015acb0e73f933242b70f24f91034"
integrity sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==
dependencies:
acorn-node "^1.8.2"
defined "^1.0.0"
minimist "^1.2.6"
didyoumean@^1.2.2: didyoumean@^1.2.2:
version "1.2.2" version "1.2.2"
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
@ -8532,10 +8507,10 @@ eslint-plugin-react@^7.25.1:
resolve "^2.0.0-next.3" resolve "^2.0.0-next.3"
string.prototype.matchall "^4.0.5" string.prototype.matchall "^4.0.5"
eslint-plugin-tailwindcss@^3.8.3: eslint-plugin-tailwindcss@^3.10.1:
version "3.8.3" version "3.10.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-3.8.3.tgz#26dfa15fec45a94ddec99e44b1f25161a3477cbd" resolved "https://registry.yarnpkg.com/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-3.10.1.tgz#727193f78cc22b9b358ef5f99de2177d173a5209"
integrity sha512-wfzfCmc9yONNW+TqfR+QWZ+syFPQ8zMOrIGx500lS4XITEm0HJYGyKh1sC1tQ9+Wmt58bnHzW3Yc31vy5RlJww== integrity sha512-NLPZ6b6nd/8CgGNMQ6NDiPUfBLQpSGu/u9RyX3MCZOwzNs2dFt1OamNAiRuo3Ixh7Gv4t5UcAcdNt8z74UDJkA==
dependencies: dependencies:
fast-glob "^3.2.5" fast-glob "^3.2.5"
postcss "^8.4.4" postcss "^8.4.4"
@ -9573,6 +9548,18 @@ glob-to-regexp@^0.4.1:
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
glob@7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.1.2, glob@^7.1.3: glob@^7.1.2, glob@^7.1.3:
version "7.1.7" version "7.1.7"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
@ -11647,6 +11634,11 @@ jest@^29.0.0:
import-local "^3.0.2" import-local "^3.0.2"
jest-cli "^29.3.1" jest-cli "^29.3.1"
jiti@^1.17.2:
version "1.18.2"
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.18.2.tgz#80c3ef3d486ebf2450d9335122b32d121f2a83cd"
integrity sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==
js-base64@^3.6.0: js-base64@^3.6.0:
version "3.7.5" version "3.7.5"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.5.tgz#21e24cf6b886f76d6f5f165bfcd69cc55b9e3fca" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.5.tgz#21e24cf6b886f76d6f5f165bfcd69cc55b9e3fca"
@ -12853,6 +12845,15 @@ multicast-dns@^7.2.4:
dns-packet "^5.2.2" dns-packet "^5.2.2"
thunky "^1.0.2" thunky "^1.0.2"
mz@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==
dependencies:
any-promise "^1.0.0"
object-assign "^4.0.1"
thenify-all "^1.0.0"
nan@^2.12.1: nan@^2.12.1:
version "2.17.0" version "2.17.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
@ -14196,7 +14197,7 @@ postcss@^7.0.14, postcss@^7.0.26, postcss@^7.0.32, postcss@^7.0.35, postcss@^7.0
picocolors "^0.2.1" picocolors "^0.2.1"
source-map "^0.6.1" source-map "^0.6.1"
postcss@^8.2.15, postcss@^8.4.4: postcss@^8.0.9, postcss@^8.2.15, postcss@^8.4.4:
version "8.4.21" version "8.4.21"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4"
integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==
@ -14214,15 +14215,6 @@ postcss@^8.4.14, postcss@^8.4.7:
picocolors "^1.0.0" picocolors "^1.0.0"
source-map-js "^1.0.2" source-map-js "^1.0.2"
postcss@^8.4.17:
version "8.4.18"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.18.tgz#6d50046ea7d3d66a85e0e782074e7203bc7fbca2"
integrity sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==
dependencies:
nanoid "^3.3.4"
picocolors "^1.0.0"
source-map-js "^1.0.2"
postcss@^8.4.19: postcss@^8.4.19:
version "8.4.20" version "8.4.20"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.20.tgz#64c52f509644cecad8567e949f4081d98349dc56" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.20.tgz#64c52f509644cecad8567e949f4081d98349dc56"
@ -14951,13 +14943,6 @@ react-textarea-autosize@^8.3.4:
use-composed-ref "^1.3.0" use-composed-ref "^1.3.0"
use-latest "^1.2.1" use-latest "^1.2.1"
react-toggle@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/react-toggle/-/react-toggle-4.1.2.tgz#b00500832f925ad524356d909821821ae39f6c52"
integrity sha512-4Ohw31TuYQdhWfA6qlKafeXx3IOH7t4ZHhmRdwsm1fQREwOBGxJT+I22sgHqR/w8JRdk+AeMCJXPImEFSrNXow==
dependencies:
classnames "^2.2.5"
react-transition-group@^2.2.1: react-transition-group@^2.2.1:
version "2.9.0" version "2.9.0"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d"
@ -16596,6 +16581,18 @@ substring-trie@^1.0.2:
resolved "https://registry.yarnpkg.com/substring-trie/-/substring-trie-1.0.2.tgz#7b42592391628b4f2cb17365c6cce4257c7b7af5" resolved "https://registry.yarnpkg.com/substring-trie/-/substring-trie-1.0.2.tgz#7b42592391628b4f2cb17365c6cce4257c7b7af5"
integrity sha1-e0JZI5Fii08ssXNlxszkJXx7evU= integrity sha1-e0JZI5Fii08ssXNlxszkJXx7evU=
sucrase@^3.29.0:
version "3.31.0"
resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.31.0.tgz#daae4fd458167c5d4ba1cce6aef57b988b417b33"
integrity sha512-6QsHnkqyVEzYcaiHsOKkzOtOgdJcb8i54x6AV2hDwyZcY9ZyykGZVw6L/YN98xC0evwTP6utsWWrKRaa8QlfEQ==
dependencies:
commander "^4.0.0"
glob "7.1.6"
lines-and-columns "^1.1.6"
mz "^2.7.0"
pirates "^4.0.1"
ts-interface-checker "^0.1.9"
supports-color@^5.0.0, supports-color@^5.3.0: supports-color@^5.0.0, supports-color@^5.3.0:
version "5.5.0" version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@ -16697,34 +16694,35 @@ table@^6.8.1:
string-width "^4.2.3" string-width "^4.2.3"
strip-ansi "^6.0.1" strip-ansi "^6.0.1"
tailwindcss@^3.2.1: tailwindcss@^3.3.1:
version "3.2.1" version "3.3.1"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.2.1.tgz#1bd828fff3172489962357f8d531c184080a6786" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.1.tgz#b6662fab6a9b704779e48d083a9fef5a81d2b81e"
integrity sha512-Uw+GVSxp5CM48krnjHObqoOwlCt5Qo6nw1jlCRwfGy68dSYb/LwS9ZFidYGRiM+w6rMawkZiu1mEMAsHYAfoLg== integrity sha512-Vkiouc41d4CEq0ujXl6oiGFQ7bA3WEhUZdTgXAhtKxSy49OmKs8rEfQmupsfF0IGW8fv2iQkp1EVUuapCFrZ9g==
dependencies: dependencies:
arg "^5.0.2" arg "^5.0.2"
chokidar "^3.5.3" chokidar "^3.5.3"
color-name "^1.1.4" color-name "^1.1.4"
detective "^5.2.1"
didyoumean "^1.2.2" didyoumean "^1.2.2"
dlv "^1.1.3" dlv "^1.1.3"
fast-glob "^3.2.12" fast-glob "^3.2.12"
glob-parent "^6.0.2" glob-parent "^6.0.2"
is-glob "^4.0.3" is-glob "^4.0.3"
jiti "^1.17.2"
lilconfig "^2.0.6" lilconfig "^2.0.6"
micromatch "^4.0.5" micromatch "^4.0.5"
normalize-path "^3.0.0" normalize-path "^3.0.0"
object-hash "^3.0.0" object-hash "^3.0.0"
picocolors "^1.0.0" picocolors "^1.0.0"
postcss "^8.4.17" postcss "^8.0.9"
postcss-import "^14.1.0" postcss-import "^14.1.0"
postcss-js "^4.0.0" postcss-js "^4.0.0"
postcss-load-config "^3.1.4" postcss-load-config "^3.1.4"
postcss-nested "6.0.0" postcss-nested "6.0.0"
postcss-selector-parser "^6.0.10" postcss-selector-parser "^6.0.11"
postcss-value-parser "^4.2.0" postcss-value-parser "^4.2.0"
quick-lru "^5.1.1" quick-lru "^5.1.1"
resolve "^1.22.1" resolve "^1.22.1"
sucrase "^3.29.0"
tapable@^1.0.0, tapable@^1.1.3: tapable@^1.0.0, tapable@^1.1.3:
version "1.1.3" version "1.1.3"
@ -16858,6 +16856,20 @@ text-table@^0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
thenify-all@^1.0.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==
dependencies:
thenify ">= 3.1.0 < 4"
"thenify@>= 3.1.0 < 4":
version "3.3.1"
resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f"
integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==
dependencies:
any-promise "^1.0.0"
through2@^2.0.0: through2@^2.0.0:
version "2.0.5" version "2.0.5"
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
@ -17031,6 +17043,11 @@ ts-dedent@^2.0.0, ts-dedent@^2.2.0:
resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5" resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5"
integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ== integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==
ts-interface-checker@^0.1.9:
version "0.1.13"
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
ts-jest@^29.0.0: ts-jest@^29.0.0:
version "29.0.3" version "29.0.3"
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.0.3.tgz#63ea93c5401ab73595440733cefdba31fcf9cb77" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.0.3.tgz#63ea93c5401ab73595440733cefdba31fcf9cb77"
@ -18333,7 +18350,7 @@ xmlchars@^2.2.0:
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1: xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
version "4.0.2" version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==