Big Buffet readwrite fork

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2023-07-12 20:33:55 +02:00
parent fe08f06e5e
commit a530d3dfb2
16 changed files with 86 additions and 149 deletions

View file

@ -290,8 +290,9 @@ const Account = ({
) : withAccountNote && (
<Text
size='sm'
truncate
dangerouslySetInnerHTML={{ __html: account.note_emojified }}
className='mr-2 rtl:ml-2 rtl:mr-0'
className='mr-2 line-clamp-2 rtl:ml-2 rtl:mr-0'
/>
)}
</Stack>

View file

@ -139,7 +139,7 @@ export const ProfileHoverCard: React.FC<IProfileHoverCard> = ({ visible = true }
) : null}
{account.note.length > 0 && (
<Text size='sm' dangerouslySetInnerHTML={accountBio} />
<Text className='line-clamp-2' truncate size='sm' dangerouslySetInnerHTML={accountBio} />
)}
</Stack>

View file

@ -24,15 +24,12 @@ const messages = defineMessages({
follows: { id: 'account.follows', defaultMessage: 'Follows' },
profile: { id: 'account.profile', defaultMessage: 'Profile' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
domainBlocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' },
followedTags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' },
soapboxConfig: { id: 'navigation_bar.soapbox_config', defaultMessage: 'Soapbox config' },
accountMigration: { id: 'navigation_bar.account_migration', defaultMessage: 'Move account' },
accountAliases: { id: 'navigation_bar.account_aliases', defaultMessage: 'Account aliases' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
profileDirectory: { id: 'navigation_bar.profile_directory', defaultMessage: 'Profile directory' },
bookmarks: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' },
lists: { id: 'column.lists', defaultMessage: 'Lists' },
groups: { id: 'column.groups', defaultMessage: 'Groups' },
@ -236,6 +233,15 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
/>
)}
{features.profileDirectory && (
<SidebarLink
to='/directory'
icon={require('@tabler/icons/address-book.svg')}
text={intl.formatMessage(messages.profileDirectory)}
onClick={onClose}
/>
)}
{settings.get('isDeveloper') && (
<SidebarLink
to='/developers'
@ -267,20 +273,6 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
<Divider />
<SidebarLink
to='/blocks'
icon={require('@tabler/icons/ban.svg')}
text={intl.formatMessage(messages.blocks)}
onClick={onClose}
/>
<SidebarLink
to='/mutes'
icon={require('@tabler/icons/circle-x.svg')}
text={intl.formatMessage(messages.mutes)}
onClick={onClose}
/>
<SidebarLink
to='/settings/preferences'
icon={require('@tabler/icons/settings.svg')}
@ -288,24 +280,6 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
onClick={onClose}
/>
{features.federating && (
<SidebarLink
to='/domain_blocks'
icon={require('@tabler/icons/ban.svg')}
text={intl.formatMessage(messages.domainBlocks)}
onClick={onClose}
/>
)}
{(features.filters || features.filtersV2) && (
<SidebarLink
to='/filters'
icon={require('@tabler/icons/filter.svg')}
text={intl.formatMessage(messages.filters)}
onClick={onClose}
/>
)}
{features.followedHashtagsList && (
<SidebarLink
to='/followed_tags'

View file

@ -1,9 +1,11 @@
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { Stack } from 'soapbox/components/ui';
import Account from 'soapbox/components/account';
import { HStack, Icon, Stack } from 'soapbox/components/ui';
import { useStatContext } from 'soapbox/contexts/stat-context';
import ComposeButton from 'soapbox/features/ui/components/compose-button';
import ProfileDropdown from 'soapbox/features/ui/components/profile-dropdown';
import { useAppSelector, useGroupsPath, useFeatures, useOwnAccount, useSettings } from 'soapbox/hooks';
import DropdownMenu, { Menu } from './dropdown-menu';
@ -14,6 +16,7 @@ const messages = defineMessages({
bookmarks: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' },
lists: { id: 'column.lists', defaultMessage: 'Lists' },
events: { id: 'column.events', defaultMessage: 'Events' },
profileDirectory: { id: 'navigation_bar.profile_directory', defaultMessage: 'Profile directory' },
developers: { id: 'navigation.developers', defaultMessage: 'Developers' },
});
@ -68,6 +71,14 @@ const SidebarNavigation = () => {
});
}
if (features.profileDirectory) {
menu.push({
to: '/directory',
text: intl.formatMessage(messages.profileDirectory),
icon: require('@tabler/icons/address-book.svg'),
});
}
if (settings.get('isDeveloper')) {
menu.push({
to: '/developers',
@ -110,7 +121,7 @@ const SidebarNavigation = () => {
};
return (
<Stack space={4}>
<Stack className='h-full' space={4}>
<Stack space={2}>
<SidebarNavigationLink
to='/'
@ -197,6 +208,22 @@ const SidebarNavigation = () => {
{account && (
<ComposeButton />
)}
<div className='grow' />
{account && (
<div className='relative items-center'>
<ProfileDropdown account={account}>
<HStack className='w-full' alignItems='center' justifyContent='between' space={2}>
<Account account={account} showProfileHoverCard={false} withLinkToProfile={false} hideActions />
<Icon
src={require('@tabler/icons/chevron-down.svg')}
className='h-4 w-4 text-gray-900 transition-transform dark:text-gray-100'
/>
</HStack>
</ProfileDropdown>
</div>
)}
</Stack>
);
};

View file

@ -102,7 +102,7 @@ const Column: React.FC<IColumn> = React.forwardRef((props, ref: React.ForwardedR
backHref={backHref}
className={clsx({
'rounded-t-3xl': !isScrolled && !transparent,
'sticky top-12 z-10 bg-white/90 dark:bg-primary-900/90 backdrop-blur lg:top-16': !transparent,
'sticky top-0 z-10 bg-white/90 dark:bg-primary-900/90 backdrop-blur': !transparent,
'p-4 sm:p-0 sm:pb-4': transparent,
'-mt-4 -mx-4 p-4': size !== 'lg' && !transparent,
'-mt-4 -mx-4 p-4 sm:-mt-6 sm:-mx-6 sm:p-6': size === 'lg' && !transparent,

View file

@ -21,7 +21,7 @@ interface LayoutComponent extends React.FC<ILayout> {
/** Layout container, to hold Sidebar, Main, and Aside. */
const Layout: LayoutComponent = ({ children }) => (
<div className='relative sm:pt-4'>
<div className='relative sm:pt-2'>
<div className='mx-auto max-w-3xl sm:px-6 md:grid md:max-w-7xl md:grid-cols-12 md:gap-8 md:px-8'>
{children}
</div>
@ -31,7 +31,7 @@ const Layout: LayoutComponent = ({ children }) => (
/** Left sidebar container in the UI. */
const Sidebar: React.FC<ISidebar> = ({ children }) => (
<div className='hidden lg:col-span-3 lg:block'>
<StickyBox offsetTop={80} className='pb-4'>
<StickyBox className='pb-4'>
{children}
</StickyBox>
</div>
@ -51,7 +51,7 @@ const Main: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ children, classN
/** Right sidebar container in the UI. */
const Aside: React.FC<IAside> = ({ children }) => (
<aside className='hidden xl:col-span-3 xl:block'>
<StickyBox offsetTop={80} className='space-y-6 pb-12'>
<StickyBox className='space-y-6 pb-12'>
{children}
</StickyBox>
</aside>

View file

@ -57,7 +57,7 @@ const AccountCard: React.FC<IAccountCard> = ({ id }) => {
<Text
truncate
align='left'
className={clsx('[&_br]:hidden [&_p:first-child]:inline [&_p:first-child]:truncate [&_p]:hidden')}
className={clsx('line-clamp-2 [&_br]:hidden [&_p:first-child]:block [&_p]:hidden')}
dangerouslySetInnerHTML={{ __html: account.note_emojified || '&nbsp;' }}
/>
</Stack>

View file

@ -20,15 +20,17 @@ const messages = defineMessages({
changePassword: { id: 'settings.change_password', defaultMessage: 'Change Password' },
configureMfa: { id: 'settings.configure_mfa', defaultMessage: 'Configure MFA' },
deleteAccount: { id: 'settings.delete_account', defaultMessage: 'Delete Account' },
domainBlocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
editProfile: { id: 'settings.edit_profile', defaultMessage: 'Edit Profile' },
exportData: { id: 'column.export_data', defaultMessage: 'Export data' },
filters: { id: 'navigation_bar.filters', defaultMessage: 'Filters' },
importData: { id: 'navigation_bar.import_data', defaultMessage: 'Import data' },
mfaDisabled: { id: 'mfa.disabled', defaultMessage: 'Disabled' },
mfaEnabled: { id: 'mfa.enabled', defaultMessage: 'Enabled' },
mutes: { id: 'settings.mutes', defaultMessage: 'Mutes' },
mutesAndBlocks: { id: 'settings.mutes_blocks', defaultMessage: 'Mutes and blocks' },
other: { id: 'settings.other', defaultMessage: 'Other options' },
preferences: { id: 'settings.preferences', defaultMessage: 'Preferences' },
privacy: { id: 'settings.privacy', defaultMessage: 'Privacy' },
profile: { id: 'settings.profile', defaultMessage: 'Profile' },
security: { id: 'settings.security', defaultMessage: 'Security' },
sessions: { id: 'settings.sessions', defaultMessage: 'Active sessions' },
@ -58,6 +60,8 @@ const Settings = () => {
const navigateToExportData = () => history.push('/settings/export');
const navigateToMutes = () => history.push('/mutes');
const navigateToBlocks = () => history.push('/blocks');
const navigateToFilters = () => history.push('/filters');
const navigateToDomainBlocks = () => history.push('/domain_blocks');
const isMfaEnabled = mfa.getIn(['settings', 'totp']);
@ -85,13 +89,15 @@ const Settings = () => {
</CardBody>
<CardHeader>
<CardTitle title={intl.formatMessage(messages.privacy)} />
<CardTitle title={intl.formatMessage(messages.mutesAndBlocks)} />
</CardHeader>
<CardBody>
<List>
<ListItem label={intl.formatMessage(messages.mutes)} onClick={navigateToMutes} />
<ListItem label={intl.formatMessage(messages.blocks)} onClick={navigateToBlocks} />
{(features.filters || features.filtersV2) && <ListItem label={intl.formatMessage(messages.filters)} onClick={navigateToFilters} />}
{features.federating && <ListItem label={intl.formatMessage(messages.domainBlocks)} onClick={navigateToDomainBlocks} />}
</List>
</CardBody>

View file

@ -40,20 +40,9 @@ const LinkFooter: React.FC = (): JSX.Element => {
<div className='space-y-2'>
<div className='divide-x-dot flex flex-wrap items-center text-gray-600'>
{account && <>
{features.profileDirectory && (
<FooterLink to='/directory'><FormattedMessage id='navigation_bar.profile_directory' defaultMessage='Profile directory' /></FooterLink>
)}
<FooterLink to='/blocks'><FormattedMessage id='navigation_bar.blocks' defaultMessage='Blocks' /></FooterLink>
<FooterLink to='/mutes'><FormattedMessage id='navigation_bar.mutes' defaultMessage='Mutes' /></FooterLink>
{(features.filters || features.filtersV2) && (
<FooterLink to='/filters'><FormattedMessage id='navigation_bar.filters' defaultMessage='Filters' /></FooterLink>
)}
{features.followedHashtagsList && (
<FooterLink to='/followed_tags'><FormattedMessage id='navigation_bar.followed_tags' defaultMessage='Followed hashtags' /></FooterLink>
)}
{features.federating && (
<FooterLink to='/domain_blocks'><FormattedMessage id='navigation_bar.domain_blocks' defaultMessage='Domain blocks' /></FooterLink>
)}
{account.admin && (
<FooterLink to='/soapbox/config'><FormattedMessage id='navigation_bar.soapbox_config' defaultMessage='Soapbox config' /></FooterLink>
)}

View file

@ -112,10 +112,6 @@ const HotkeysModal: React.FC<IHotkeysModal> = ({ onClose }) => {
<TableCell><Hotkey>backspace</Hotkey></TableCell>
<TableCell><FormattedMessage id='keyboard_shortcuts.back' defaultMessage='to navigate back' /></TableCell>
</tr>
<tr>
<TableCell><Hotkey>s</Hotkey>, <Hotkey>/</Hotkey></TableCell>
<TableCell><FormattedMessage id='keyboard_shortcuts.search' defaultMessage='to focus search' /></TableCell>
</tr>
<tr>
<TableCell><Hotkey>esc</Hotkey></TableCell>
<TableCell><FormattedMessage id='keyboard_shortcuts.unfocus' defaultMessage='to un-focus compose textarea/search' /></TableCell>
@ -133,9 +129,13 @@ const HotkeysModal: React.FC<IHotkeysModal> = ({ onClose }) => {
<TableCell><Hotkey>g</Hotkey> + <Hotkey>h</Hotkey></TableCell>
<TableCell><FormattedMessage id='keyboard_shortcuts.home' defaultMessage='to open home timeline' /></TableCell>
</tr>
<tr>
<TableCell><Hotkey>g</Hotkey> + <Hotkey>s</Hotkey></TableCell>
<TableCell><FormattedMessage id='keyboard_shortcuts.search' defaultMessage='to open search page' /></TableCell>
</tr>
<tr>
<TableCell><Hotkey>g</Hotkey> + <Hotkey>n</Hotkey></TableCell>
<TableCell><FormattedMessage id='keyboard_shortcuts.notifications' defaultMessage='to open notifications column' /></TableCell>
<TableCell><FormattedMessage id='keyboard_shortcuts.notifications' defaultMessage='to open notifications list' /></TableCell>
</tr>
<tr>
<TableCell><Hotkey>g</Hotkey> + <Hotkey>f</Hotkey></TableCell>

View file

@ -8,11 +8,8 @@ import { fetchInstance } from 'soapbox/actions/instance';
import { openSidebar } from 'soapbox/actions/sidebar';
import SiteLogo from 'soapbox/components/site-logo';
import { Avatar, Button, Form, HStack, IconButton, Input, Tooltip } from 'soapbox/components/ui';
import Search from 'soapbox/features/compose/components/search';
import { useAppDispatch, useFeatures, useOwnAccount, useRegistrationStatus } from 'soapbox/hooks';
import ProfileDropdown from './profile-dropdown';
import type { AxiosError } from 'axios';
const messages = defineMessages({
@ -65,11 +62,11 @@ const Navbar = () => {
if (mfaToken) return <Redirect to={`/login?token=${encodeURIComponent(mfaToken)}`} />;
return (
<nav className='sticky top-0 z-50 bg-white shadow dark:bg-primary-900' ref={node} data-testid='navbar'>
<nav className={clsx('sticky top-0 z-50 bg-white shadow dark:bg-primary-900', { 'lg:hidden': !!account })} ref={node} data-testid='navbar'>
<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'>
{account && (
<div className='absolute inset-y-0 left-0 flex items-center rtl:left-auto rtl:right-0 lg:hidden'>
<div className='absolute inset-y-0 left-0 flex items-center rtl:left-auto rtl:right-0'>
<button onClick={onOpenSidebar}>
<Avatar src={account.avatar} size={34} />
</button>
@ -88,24 +85,10 @@ const Navbar = () => {
<SiteLogo alt='Logo' className='h-5 w-auto cursor-pointer' />
<span className='hidden'><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></span>
</Link>
{account && (
<div className='hidden flex-1 items-center justify-center px-2 lg:ml-6 lg:flex lg:justify-start'>
<div className='hidden w-full max-w-xl lg:block lg:max-w-xs'>
<Search openInRoute autosuggest />
</div>
</div>
)}
</HStack>
<HStack space={3} alignItems='center' className='absolute inset-y-0 right-0 pr-2 lg:static lg:inset-auto lg:ml-6 lg:pr-0'>
{account ? (
<div className='relative hidden items-center lg:flex'>
<ProfileDropdown account={account}>
<Avatar src={account.avatar} size={34} />
</ProfileDropdown>
</div>
) : (
{!account && (
<>
<Form className='hidden items-center space-x-2 rtl:space-x-reverse lg:flex' onSubmit={handleSubmit}>
<Input

View file

@ -42,7 +42,7 @@ const ProfileDropdown: React.FC<IProfileDropdown> = ({ account, children }) => {
const intl = useIntl();
const [visible, setVisible] = useState(false);
const { x, y, strategy, refs } = useFloating<HTMLButtonElement>({ placement: 'bottom-end' });
const { x, y, strategy, refs } = useFloating<HTMLButtonElement>({ placement: 'top-start' });
const authUsers = useAppSelector((state) => state.auth.users);
const otherAccounts = useAppSelector((state) => authUsers.map((authUser: any) => getAccount(state, authUser.id)!));

View file

@ -10,10 +10,11 @@ import type { Account } from 'soapbox/schemas';
const messages = defineMessages({
followers: { id: 'account.followers', defaultMessage: 'Followers' },
follows: { id: 'account.follows', defaultMessage: 'Follows' },
statuses: { id: 'account.statuses', defaultMessage: 'Statuses' },
});
interface IProfileStats {
account: Pick<Account, 'acct' | 'followers_count' | 'following_count'> | undefined
account: Pick<Account, 'acct' | 'followers_count' | 'following_count' | 'statuses_count'> | undefined
onClickHandler?: React.MouseEventHandler
}
@ -27,6 +28,15 @@ const ProfileStats: React.FC<IProfileStats> = ({ account, onClickHandler }) => {
return (
<HStack alignItems='center' space={3}>
<HStack alignItems='center' space={1}>
<Text theme='primary' weight='bold' size='sm'>
{shortNumberFormat(account.statuses_count)}
</Text>
<Text weight='bold' size='sm'>
{intl.formatMessage(messages.statuses)}
</Text>
</HStack>
<NavLink to={`/@${account.acct}/followers`} onClick={onClickHandler} title={intl.formatNumber(account.followers_count)} className='hover:underline'>
<HStack alignItems='center' space={1}>
<Text theme='primary' weight='bold' size='sm'>

View file

@ -9,7 +9,6 @@ import { useAppSelector, useAppDispatch, useOwnAccount } from 'soapbox/hooks';
const keyMap = {
help: '?',
new: 'n',
search: ['s', '/'],
forceNew: 'option+n',
reply: 'r',
favourite: 'f',
@ -29,6 +28,7 @@ const keyMap = {
goToBlocked: 'g b',
goToMuted: 'g m',
goToRequests: 'g r',
goToSearch: 'g s',
toggleHidden: 'x',
toggleSensitive: 'h',
openMedia: 'a',
@ -58,17 +58,6 @@ const GlobalHotkeys: React.FC<IGlobalHotkeys> = ({ children, node }) => {
}
};
const handleHotkeySearch = (e?: KeyboardEvent) => {
e?.preventDefault();
if (!node.current) return;
const element = node.current.querySelector('input#search') as HTMLInputElement;
if (element) {
element.focus();
}
};
const handleHotkeyForceNew = (e?: KeyboardEvent) => {
handleHotkeyNew(e);
dispatch(resetCompose());
@ -132,12 +121,16 @@ const GlobalHotkeys: React.FC<IGlobalHotkeys> = ({ children, node }) => {
history.push('/follow_requests');
};
const handleHotkeyGoToSearch = () => {
if (!account) return;
history.push('/search');
};
type HotkeyHandlers = { [key: string]: (keyEvent?: KeyboardEvent) => void };
const handlers: HotkeyHandlers = {
help: handleHotkeyToggleHelp,
new: handleHotkeyNew,
search: handleHotkeySearch,
forceNew: handleHotkeyForceNew,
back: handleHotkeyBack,
goToHome: handleHotkeyGoToHome,
@ -148,6 +141,7 @@ const GlobalHotkeys: React.FC<IGlobalHotkeys> = ({ children, node }) => {
goToBlocked: handleHotkeyGoToBlocked,
goToMuted: handleHotkeyGoToMuted,
goToRequests: handleHotkeyGoToRequests,
goToSearch: handleHotkeyGoToSearch,
};
return (

View file

@ -1,9 +1,5 @@
import clsx from 'clsx';
import React, { useRef } from 'react';
import { useIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import React from 'react';
import { uploadCompose } from 'soapbox/actions/compose';
import FeedCarousel from 'soapbox/features/feed-filtering/feed-carousel';
import LinkFooter from 'soapbox/features/ui/components/link-footer';
import {
@ -17,10 +13,9 @@ import {
CtaBanner,
AnnouncementsPanel,
} from 'soapbox/features/ui/util/async-components';
import { useAppSelector, useOwnAccount, useFeatures, useSoapboxConfig, useDraggedFiles, useAppDispatch } from 'soapbox/hooks';
import { useAppSelector, useFeatures, useSoapboxConfig } from 'soapbox/hooks';
import { Avatar, Card, CardBody, HStack, Layout } from '../components/ui';
import ComposeForm from '../features/compose/components/compose-form';
import { Layout } from '../components/ui';
import BundleContainer from '../features/ui/containers/bundle-container';
interface IHomePage {
@ -28,59 +23,17 @@ interface IHomePage {
}
const HomePage: React.FC<IHomePage> = ({ children }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const me = useAppSelector(state => state.me);
const { account } = useOwnAccount();
const features = useFeatures();
const soapboxConfig = useSoapboxConfig();
const composeId = 'home';
const composeBlock = useRef<HTMLDivElement>(null);
const hasPatron = soapboxConfig.extensions.getIn(['patron', 'enabled']) === true;
const hasCrypto = typeof soapboxConfig.cryptoAddresses.getIn([0, 'ticker']) === 'string';
const cryptoLimit = soapboxConfig.cryptoDonatePanel.get('limit', 0);
const { isDragging, isDraggedOver } = useDraggedFiles(composeBlock, (files) => {
dispatch(uploadCompose(composeId, files, intl));
});
const acct = account ? account.acct : '';
const avatar = account ? account.avatar : '';
return (
<>
<Layout.Main className='space-y-3 pt-3 dark:divide-gray-800 sm:pt-0'>
{me && (
<Card
className={clsx('relative z-[1] transition', {
'border-2 border-primary-600 border-dashed z-[99]': isDragging,
'ring-2 ring-offset-2 ring-primary-600': isDraggedOver,
})}
variant='rounded'
ref={composeBlock}
>
<CardBody>
<HStack alignItems='start' space={4}>
<Link to={`/@${acct}`}>
<Avatar src={avatar} size={46} />
</Link>
<div className='w-full translate-y-0.5'>
<ComposeForm
id={composeId}
shouldCondense
autoFocus={false}
clickableAreaRef={composeBlock}
/>
</div>
</HStack>
</CardBody>
</Card>
)}
{features.carousel && <FeedCarousel />}
{children}

View file

@ -154,4 +154,4 @@ const accountSchema = baseAccountSchema.extend({
type Account = Resolve<z.infer<typeof accountSchema>>;
export { accountSchema, type Account };
export { accountSchema, type Account };