163 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			163 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import type { MouseEventHandler } from 'react';
 | |
| import { useCallback, useState } from 'react';
 | |
| 
 | |
| import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
 | |
| 
 | |
| import classNames from 'classnames';
 | |
| import { useHistory } from 'react-router';
 | |
| 
 | |
| import type Immutable from 'immutable';
 | |
| 
 | |
| import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
 | |
| import AttachmentList from 'mastodon/components/attachment_list';
 | |
| import { Icon } from 'mastodon/components/icon';
 | |
| import { VisibilityIcon } from 'mastodon/components/visibility_icon';
 | |
| import PrivacyDropdown from 'mastodon/features/compose/components/privacy_dropdown';
 | |
| import type { Account } from 'mastodon/models/account';
 | |
| import type { Status, StatusVisibility } from 'mastodon/models/status';
 | |
| import { useAppSelector } from 'mastodon/store';
 | |
| 
 | |
| import { Avatar } from '../../../components/avatar';
 | |
| import { Button } from '../../../components/button';
 | |
| import { DisplayName } from '../../../components/display_name';
 | |
| import { RelativeTimestamp } from '../../../components/relative_timestamp';
 | |
| import StatusContent from '../../../components/status_content';
 | |
| 
 | |
| const messages = defineMessages({
 | |
|   cancel_reblog: {
 | |
|     id: 'status.cancel_reblog_private',
 | |
|     defaultMessage: 'Unboost',
 | |
|   },
 | |
|   reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
 | |
| });
 | |
| 
 | |
| export const BoostModal: React.FC<{
 | |
|   status: Status;
 | |
|   onClose: () => void;
 | |
|   onReblog: (status: Status, privacy: StatusVisibility) => void;
 | |
| }> = ({ status, onReblog, onClose }) => {
 | |
|   const intl = useIntl();
 | |
|   const history = useHistory();
 | |
| 
 | |
|   const default_privacy = useAppSelector(
 | |
|     // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
 | |
|     (state) => state.compose.get('default_privacy') as StatusVisibility,
 | |
|   );
 | |
| 
 | |
|   const account = status.get('account') as Account;
 | |
|   const statusVisibility = status.get('visibility') as StatusVisibility;
 | |
| 
 | |
|   const [privacy, setPrivacy] = useState<StatusVisibility>(
 | |
|     statusVisibility === 'private' ? 'private' : default_privacy,
 | |
|   );
 | |
| 
 | |
|   const onPrivacyChange = useCallback((value: StatusVisibility) => {
 | |
|     setPrivacy(value);
 | |
|   }, []);
 | |
| 
 | |
|   const handleReblog = useCallback(() => {
 | |
|     onReblog(status, privacy);
 | |
|     onClose();
 | |
|   }, [onClose, onReblog, status, privacy]);
 | |
| 
 | |
|   const handleAccountClick = useCallback<MouseEventHandler>(
 | |
|     (e) => {
 | |
|       if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
 | |
|         e.preventDefault();
 | |
|         onClose();
 | |
|         history.push(`/@${account.acct}`);
 | |
|       }
 | |
|     },
 | |
|     [history, onClose, account],
 | |
|   );
 | |
| 
 | |
|   const buttonText = status.get('reblogged')
 | |
|     ? messages.cancel_reblog
 | |
|     : messages.reblog;
 | |
| 
 | |
|   const findContainer = useCallback(
 | |
|     () => document.getElementsByClassName('modal-root__container')[0],
 | |
|     [],
 | |
|   );
 | |
| 
 | |
|   return (
 | |
|     <div className='modal-root__modal boost-modal'>
 | |
|       <div className='boost-modal__container'>
 | |
|         <div
 | |
|           className={classNames(
 | |
|             'status',
 | |
|             `status-${statusVisibility}`,
 | |
|             'light',
 | |
|           )}
 | |
|         >
 | |
|           <div className='status__info'>
 | |
|             <a
 | |
|               href={`/@${account.acct}/${status.get('id') as string}`}
 | |
|               className='status__relative-time'
 | |
|               target='_blank'
 | |
|               rel='noopener noreferrer'
 | |
|             >
 | |
|               <span className='status__visibility-icon'>
 | |
|                 <VisibilityIcon visibility={statusVisibility} />
 | |
|               </span>
 | |
|               <RelativeTimestamp
 | |
|                 timestamp={status.get('created_at') as string}
 | |
|               />
 | |
|             </a>
 | |
| 
 | |
|             <a
 | |
|               onClick={handleAccountClick}
 | |
|               href={`/@${account.acct}`}
 | |
|               className='status__display-name'
 | |
|             >
 | |
|               <div className='status__avatar'>
 | |
|                 <Avatar account={account} size={48} />
 | |
|               </div>
 | |
| 
 | |
|               <DisplayName account={account} />
 | |
|             </a>
 | |
|           </div>
 | |
| 
 | |
|           {/* @ts-expect-error Expected until StatusContent is typed */}
 | |
|           <StatusContent status={status} />
 | |
| 
 | |
|           {(status.get('media_attachments') as Immutable.List<unknown>).size >
 | |
|             0 && (
 | |
|             <AttachmentList compact media={status.get('media_attachments')} />
 | |
|           )}
 | |
|         </div>
 | |
|       </div>
 | |
| 
 | |
|       <div className='boost-modal__action-bar'>
 | |
|         <div>
 | |
|           <FormattedMessage
 | |
|             id='boost_modal.combo'
 | |
|             defaultMessage='You can press {combo} to skip this next time'
 | |
|             values={{
 | |
|               combo: (
 | |
|                 <span>
 | |
|                   Shift + <Icon id='retweet' icon={RepeatIcon} />
 | |
|                 </span>
 | |
|               ),
 | |
|             }}
 | |
|           />
 | |
|         </div>
 | |
|         {statusVisibility !== 'private' && !status.get('reblogged') && (
 | |
|           <PrivacyDropdown
 | |
|             noDirect
 | |
|             value={privacy}
 | |
|             container={findContainer}
 | |
|             onChange={onPrivacyChange}
 | |
|           />
 | |
|         )}
 | |
|         <Button
 | |
|           text={intl.formatMessage(buttonText)}
 | |
|           onClick={handleReblog}
 | |
|           // eslint-disable-next-line jsx-a11y/no-autofocus
 | |
|           autoFocus
 | |
|         />
 | |
|       </div>
 | |
|     </div>
 | |
|   );
 | |
| };
 |