Merge commit '7f16397f3c37a8e378239974b73afbfe2b6e6844' into glitch-soc/merge-upstream
Conflicts: - `lib/mastodon/version.rb`: Upstream bumped the mastodon API version, glitch-soc has a change on an adjacent line adding a glitch API version.
This commit is contained in:
commit
0e1ea3110e
86 files changed
+1440
-273
No files matched your search
@@ -0,0 +1,122 @@
|
||||
import type { ChangeEventHandler, FC } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { Callout } from '@/mastodon/components/callout';
|
||||
import { ToggleField } from '@/mastodon/components/form_fields';
|
||||
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
|
||||
import { patchProfile } from '@/mastodon/reducers/slices/profile_edit';
|
||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||
|
||||
import type { DialogModalProps } from '../../ui/components/dialog_modal';
|
||||
import { DialogModal } from '../../ui/components/dialog_modal';
|
||||
import { messages } from '../index';
|
||||
import classes from '../styles.module.scss';
|
||||
|
||||
export const ProfileDisplayModal: FC<DialogModalProps> = ({ onClose }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const { profile, isPending } = useAppSelector((state) => state.profileEdit);
|
||||
const serverName = useAppSelector(
|
||||
(state) => state.meta.get('domain') as string,
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const handleToggleChange: ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||
(event) => {
|
||||
const { name, checked } = event.target;
|
||||
void dispatch(patchProfile({ [name]: checked }));
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
if (!profile) {
|
||||
return <LoadingIndicator />;
|
||||
}
|
||||
|
||||
return (
|
||||
<DialogModal
|
||||
onClose={onClose}
|
||||
title={intl.formatMessage(messages.profileTabTitle)}
|
||||
noCancelButton
|
||||
>
|
||||
<div className={classes.toggleInputWrapper}>
|
||||
<ToggleField
|
||||
checked={profile.showMedia}
|
||||
onChange={handleToggleChange}
|
||||
disabled={isPending}
|
||||
name='show_media'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='account_edit.profile_tab.show_media.title'
|
||||
defaultMessage='Show ‘Media’ tab'
|
||||
/>
|
||||
}
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='account_edit.profile_tab.show_media.description'
|
||||
defaultMessage='‘Media’ is an optional tab that shows your posts containing images or videos.'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<ToggleField
|
||||
checked={profile.showMediaReplies}
|
||||
onChange={handleToggleChange}
|
||||
disabled={!profile.showMedia || isPending}
|
||||
name='show_media_replies'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='account_edit.profile_tab.show_media_replies.title'
|
||||
defaultMessage='Include replies on ‘Media’ tab'
|
||||
/>
|
||||
}
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='account_edit.profile_tab.show_media_replies.description'
|
||||
defaultMessage='When enabled, Media tab shows both your posts and replies to other people’s posts.'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<ToggleField
|
||||
checked={profile.showFeatured}
|
||||
onChange={handleToggleChange}
|
||||
disabled={isPending}
|
||||
name='show_featured'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='account_edit.profile_tab.show_featured.title'
|
||||
defaultMessage='Show ‘Featured’ tab'
|
||||
/>
|
||||
}
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='account_edit.profile_tab.show_featured.description'
|
||||
defaultMessage='‘Featured’ is an optional tab where you can showcase other accounts.'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Callout
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='account_edit.profile_tab.hint.title'
|
||||
defaultMessage='Displays still vary'
|
||||
/>
|
||||
}
|
||||
icon={false}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='account_edit.profile_tab.hint.description'
|
||||
defaultMessage='These settings customize what users see on {server} in the official apps, but they may not apply to users on other servers and 3rd party apps.'
|
||||
values={{
|
||||
server: serverName,
|
||||
}}
|
||||
/>
|
||||
</Callout>
|
||||
</DialogModal>
|
||||
);
|
||||
};
|
||||
@@ -1,13 +1,14 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import type { ModalType } from '@/mastodon/actions/modal';
|
||||
import { openModal } from '@/mastodon/actions/modal';
|
||||
import { Avatar } from '@/mastodon/components/avatar';
|
||||
import { Button } from '@/mastodon/components/button';
|
||||
import { CustomEmojiProvider } from '@/mastodon/components/emoji/context';
|
||||
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
||||
import { useElementHandledLink } from '@/mastodon/components/status/handled_link';
|
||||
@@ -25,7 +26,7 @@ import { EditButton } from './components/edit_button';
|
||||
import { AccountEditSection } from './components/section';
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
const messages = defineMessages({
|
||||
export const messages = defineMessages({
|
||||
columnTitle: {
|
||||
id: 'account_edit.column_title',
|
||||
defaultMessage: 'Edit Profile',
|
||||
@@ -104,6 +105,9 @@ export const AccountEdit: FC = () => {
|
||||
const handleBioEdit = useCallback(() => {
|
||||
handleOpenModal('ACCOUNT_EDIT_BIO');
|
||||
}, [handleOpenModal]);
|
||||
const handleProfileDisplayEdit = useCallback(() => {
|
||||
handleOpenModal('ACCOUNT_EDIT_PROFILE_DISPLAY');
|
||||
}, [handleOpenModal]);
|
||||
|
||||
const history = useHistory();
|
||||
const handleFeaturedTagsEdit = useCallback(() => {
|
||||
@@ -193,6 +197,17 @@ export const AccountEdit: FC = () => {
|
||||
title={messages.profileTabTitle}
|
||||
description={messages.profileTabSubtitle}
|
||||
showDescription
|
||||
buttons={
|
||||
<Button
|
||||
className={classes.editButton}
|
||||
onClick={handleProfileDisplayEdit}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='account_edit.profile_tab.button_label'
|
||||
defaultMessage='Customize'
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</CustomEmojiProvider>
|
||||
</AccountEditColumn>
|
||||
|
||||
@@ -90,6 +90,16 @@ textarea.inputText {
|
||||
}
|
||||
}
|
||||
|
||||
.toggleInputWrapper {
|
||||
> div {
|
||||
padding: 12px 0;
|
||||
|
||||
&:not(:first-child) {
|
||||
border-top: 1px solid var(--color-border-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Column component
|
||||
|
||||
.column {
|
||||
|
||||
@@ -118,14 +118,14 @@ const RedesignAccountHeaderFields: FC<{ account: Account }> = ({ account }) => {
|
||||
<CustomEmojiProvider emojis={emojis}>
|
||||
<dl className={classes.fieldList} ref={wrapperRef}>
|
||||
{fields.map((field, key) => (
|
||||
<FieldRow key={key} field={field} htmlHandlers={htmlHandlers} />
|
||||
<FieldCard key={key} field={field} htmlHandlers={htmlHandlers} />
|
||||
))}
|
||||
</dl>
|
||||
</CustomEmojiProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const FieldRow: FC<{
|
||||
const FieldCard: FC<{
|
||||
htmlHandlers: ReturnType<typeof useElementHandledLink>;
|
||||
field: AccountField;
|
||||
}> = ({ htmlHandlers, field }) => {
|
||||
@@ -183,15 +183,14 @@ const FieldRow: FC<{
|
||||
ref={wrapperRef}
|
||||
>
|
||||
{verified_at && (
|
||||
<Icon
|
||||
id='verified'
|
||||
icon={IconVerified}
|
||||
<span
|
||||
className={classes.fieldVerifiedIcon}
|
||||
aria-label={intl.formatMessage(verifyMessage, {
|
||||
title={intl.formatMessage(verifyMessage, {
|
||||
date: intl.formatDate(verified_at, dateFormatOptions),
|
||||
})}
|
||||
noFill
|
||||
/>
|
||||
>
|
||||
<Icon id='verified' icon={IconVerified} noFill />
|
||||
</span>
|
||||
)}
|
||||
</MiniCard>
|
||||
);
|
||||
|
||||
@@ -278,11 +278,17 @@ svg.badgeIcon {
|
||||
}
|
||||
|
||||
.fieldVerifiedIcon {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
|
||||
> svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.fieldOverflowButton {
|
||||
|
||||
@@ -2,7 +2,9 @@ import type { FC } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Button } from '@/mastodon/components/button';
|
||||
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
||||
import { ModalShell } from '@/mastodon/components/modal_shell';
|
||||
|
||||
import type { AccountField } from '../common';
|
||||
import { useFieldHtml } from '../hooks/useFieldHtml';
|
||||
@@ -16,29 +18,25 @@ export const AccountFieldModal: FC<{
|
||||
const handleLabelElement = useFieldHtml(field.nameHasEmojis);
|
||||
const handleValueElement = useFieldHtml(field.valueHasEmojis);
|
||||
return (
|
||||
<div className='modal-root__modal safety-action-modal'>
|
||||
<div className='safety-action-modal__top'>
|
||||
<div className='safety-action-modal__confirmation'>
|
||||
<EmojiHTML
|
||||
as='p'
|
||||
htmlString={field.name_emojified}
|
||||
onElement={handleLabelElement}
|
||||
/>
|
||||
<EmojiHTML
|
||||
as='p'
|
||||
htmlString={field.value_emojified}
|
||||
onElement={handleValueElement}
|
||||
className={classes.fieldValue}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='safety-action-modal__bottom'>
|
||||
<div className='safety-action-modal__actions'>
|
||||
<button onClick={onClose} className='link-button' type='button'>
|
||||
<FormattedMessage id='lightbox.close' defaultMessage='Close' />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ModalShell>
|
||||
<ModalShell.Body>
|
||||
<EmojiHTML
|
||||
as='h2'
|
||||
htmlString={field.name_emojified}
|
||||
onElement={handleLabelElement}
|
||||
/>
|
||||
<EmojiHTML
|
||||
as='p'
|
||||
htmlString={field.value_emojified}
|
||||
onElement={handleValueElement}
|
||||
className={classes.fieldValue}
|
||||
/>
|
||||
</ModalShell.Body>
|
||||
<ModalShell.Actions>
|
||||
<Button onClick={onClose} plain>
|
||||
<FormattedMessage id='lightbox.close' defaultMessage='Close' />
|
||||
</Button>
|
||||
</ModalShell.Actions>
|
||||
</ModalShell>
|
||||
);
|
||||
};
|
||||
@@ -7,6 +7,7 @@
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 8px 0;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -41,6 +42,10 @@
|
||||
align-items: center;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
[data-color-scheme='dark'] & {
|
||||
border: 1px solid var(--color-border-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.tagsWrapper {
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
--gap: 0.75ch;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--gap);
|
||||
|
||||
& > li:not(:last-child)::after {
|
||||
|
||||
@@ -3,18 +3,21 @@ import { useCallback, useEffect } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { useParams } from 'react-router';
|
||||
import { useLocation, useParams } from 'react-router';
|
||||
|
||||
import { openModal } from '@/mastodon/actions/modal';
|
||||
import { useRelationship } from '@/mastodon/hooks/useRelationship';
|
||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||
import ShareIcon from '@/material-icons/400-24px/share.svg?react';
|
||||
import { showAlert } from 'mastodon/actions/alerts';
|
||||
import type { ApiCollectionJSON } from 'mastodon/api_types/collections';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import { Avatar } from 'mastodon/components/avatar';
|
||||
import { Column } from 'mastodon/components/column';
|
||||
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||
import { LinkedDisplayName } from 'mastodon/components/display_name';
|
||||
import {
|
||||
DisplayName,
|
||||
LinkedDisplayName,
|
||||
} from 'mastodon/components/display_name';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||
import { Tag } from 'mastodon/components/tags/tag';
|
||||
@@ -46,32 +49,40 @@ const messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
const AuthorNote: React.FC<{ id: string }> = ({ id }) => {
|
||||
export const AuthorNote: React.FC<{ id: string; previewMode?: boolean }> = ({
|
||||
id,
|
||||
// When previewMode is enabled, your own display name
|
||||
// will not be replaced with "you"
|
||||
previewMode = false,
|
||||
}) => {
|
||||
const account = useAccount(id);
|
||||
const author = (
|
||||
<span className={classes.displayNameWithAvatar}>
|
||||
<Avatar size={18} account={account} />
|
||||
<LinkedDisplayName displayProps={{ account, variant: 'simple' }} />
|
||||
{previewMode ? (
|
||||
<DisplayName account={account} variant='simple' />
|
||||
) : (
|
||||
<LinkedDisplayName displayProps={{ account, variant: 'simple' }} />
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
|
||||
if (id === me) {
|
||||
return (
|
||||
<p className={classes.authorNote}>
|
||||
const displayAsYou = id === me && !previewMode;
|
||||
|
||||
return (
|
||||
<p className={previewMode ? classes.previewAuthorNote : classes.authorNote}>
|
||||
{displayAsYou ? (
|
||||
<FormattedMessage
|
||||
id='collections.detail.curated_by_you'
|
||||
defaultMessage='Curated by you'
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<p className={classes.authorNote}>
|
||||
<FormattedMessage
|
||||
id='collections.detail.curated_by_author'
|
||||
defaultMessage='Curated by {author}'
|
||||
values={{ author }}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='collections.detail.curated_by_author'
|
||||
defaultMessage='Curated by {author}'
|
||||
values={{ author }}
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
@@ -84,8 +95,23 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleShare = useCallback(() => {
|
||||
dispatch(showAlert({ message: 'Collection sharing not yet implemented' }));
|
||||
}, [dispatch]);
|
||||
dispatch(
|
||||
openModal({
|
||||
modalType: 'SHARE_COLLECTION',
|
||||
modalProps: {
|
||||
collection,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}, [collection, dispatch]);
|
||||
|
||||
const location = useLocation<{ newCollection?: boolean }>();
|
||||
const wasJustCreated = location.state.newCollection;
|
||||
useEffect(() => {
|
||||
if (wasJustCreated) {
|
||||
handleShare();
|
||||
}
|
||||
}, [handleShare, wasJustCreated]);
|
||||
|
||||
return (
|
||||
<div className={classes.header}>
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
.heading {
|
||||
font-size: 28px;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.preview {
|
||||
display: flex;
|
||||
flex-wrap: wrap-reverse;
|
||||
align-items: start;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
color: var(--color-text-primary);
|
||||
background: linear-gradient(
|
||||
145deg,
|
||||
var(--color-bg-brand-soft),
|
||||
var(--color-bg-primary)
|
||||
);
|
||||
border: 1px solid var(--color-bg-brand-base);
|
||||
}
|
||||
|
||||
.previewHeading {
|
||||
font-size: 22px;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
$bottomsheet-breakpoint: 630px;
|
||||
|
||||
.shareButtonWrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
|
||||
& > button {
|
||||
flex: 1;
|
||||
min-width: 220px;
|
||||
white-space: normal;
|
||||
|
||||
@media (width > $bottomsheet-breakpoint) {
|
||||
max-width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.closeButtonDesktop {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
inset-inline-end: 4px;
|
||||
padding: 8px;
|
||||
|
||||
@media (width <= $bottomsheet-breakpoint) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.closeButtonMobile {
|
||||
margin-top: 16px;
|
||||
margin-bottom: -18px;
|
||||
|
||||
@media (width > $bottomsheet-breakpoint) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { useLocation } from 'react-router';
|
||||
|
||||
import { me } from '@/mastodon/initial_state';
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import { changeCompose, focusCompose } from 'mastodon/actions/compose';
|
||||
import type { ApiCollectionJSON } from 'mastodon/api_types/collections';
|
||||
import { AvatarById } from 'mastodon/components/avatar';
|
||||
import { AvatarGroup } from 'mastodon/components/avatar_group';
|
||||
import { Button } from 'mastodon/components/button';
|
||||
import { CopyLinkField } from 'mastodon/components/form_fields';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import { ModalShell } from 'mastodon/components/modal_shell';
|
||||
import { useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { AuthorNote } from '.';
|
||||
import classes from './share_modal.module.scss';
|
||||
|
||||
const messages = defineMessages({
|
||||
shareTextOwn: {
|
||||
id: 'collection.share_template_own',
|
||||
defaultMessage: 'Check out my new collection: {link}',
|
||||
},
|
||||
shareTextOther: {
|
||||
id: 'collection.share_template_other',
|
||||
defaultMessage: 'Check out this cool collection: {link}',
|
||||
},
|
||||
});
|
||||
|
||||
export const CollectionShareModal: React.FC<{
|
||||
collection: ApiCollectionJSON;
|
||||
onClose: () => void;
|
||||
}> = ({ collection, onClose }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const location = useLocation<{ newCollection?: boolean }>();
|
||||
const isNew = !!location.state.newCollection;
|
||||
const isOwnCollection = collection.account_id === me;
|
||||
|
||||
const collectionLink = `${window.location.origin}/collections/${collection.id}`;
|
||||
|
||||
const handleShareOnDevice = useCallback(() => {
|
||||
void navigator.share({
|
||||
url: collectionLink,
|
||||
});
|
||||
}, [collectionLink]);
|
||||
|
||||
const handleShareViaPost = useCallback(() => {
|
||||
const shareMessage = isOwnCollection
|
||||
? intl.formatMessage(messages.shareTextOwn, {
|
||||
link: collectionLink,
|
||||
})
|
||||
: intl.formatMessage(messages.shareTextOther, {
|
||||
link: collectionLink,
|
||||
});
|
||||
|
||||
onClose();
|
||||
dispatch(changeCompose(shareMessage));
|
||||
dispatch(focusCompose());
|
||||
}, [collectionLink, dispatch, intl, isOwnCollection, onClose]);
|
||||
|
||||
return (
|
||||
<ModalShell>
|
||||
<ModalShell.Body>
|
||||
<h1 className={classes.heading}>
|
||||
{isNew ? (
|
||||
<FormattedMessage
|
||||
id='collection.share_modal.title_new'
|
||||
defaultMessage='Share your new collection!'
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='collection.share_modal.title'
|
||||
defaultMessage='Share collection'
|
||||
/>
|
||||
)}
|
||||
</h1>
|
||||
|
||||
<IconButton
|
||||
title={intl.formatMessage({
|
||||
id: 'lightbox.close',
|
||||
defaultMessage: 'Close',
|
||||
})}
|
||||
iconComponent={CloseIcon}
|
||||
icon='close'
|
||||
className={classes.closeButtonDesktop}
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
<div className={classes.preview}>
|
||||
<div>
|
||||
<h2 className={classes.previewHeading}>{collection.name}</h2>
|
||||
<AuthorNote previewMode id={collection.account_id} />
|
||||
</div>
|
||||
<AvatarGroup>
|
||||
{collection.items.slice(0, 5).map(({ account_id }) => {
|
||||
if (!account_id) return;
|
||||
return (
|
||||
<AvatarById key={account_id} accountId={account_id} size={28} />
|
||||
);
|
||||
})}
|
||||
</AvatarGroup>
|
||||
</div>
|
||||
|
||||
<CopyLinkField
|
||||
label={intl.formatMessage({
|
||||
id: 'collection.share_modal.share_link_label',
|
||||
defaultMessage: 'Invite share link',
|
||||
})}
|
||||
value={collectionLink}
|
||||
/>
|
||||
</ModalShell.Body>
|
||||
|
||||
<ModalShell.Actions className={classes.actions}>
|
||||
<div className={classes.shareButtonWrapper}>
|
||||
<Button secondary onClick={handleShareViaPost}>
|
||||
<FormattedMessage
|
||||
id='collection.share_modal.share_via_post'
|
||||
defaultMessage='Post on Mastodon'
|
||||
/>
|
||||
</Button>
|
||||
{'share' in navigator && (
|
||||
<Button secondary onClick={handleShareOnDevice}>
|
||||
<FormattedMessage
|
||||
id='collection.share_modal.share_via_system'
|
||||
defaultMessage='Share to…'
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button plain onClick={onClose} className={classes.closeButtonMobile}>
|
||||
<FormattedMessage id='lightbox.close' defaultMessage='Close' />
|
||||
</Button>
|
||||
</ModalShell.Actions>
|
||||
</ModalShell>
|
||||
);
|
||||
};
|
||||
@@ -48,6 +48,10 @@
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.previewAuthorNote {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.metaData {
|
||||
margin-top: 16px;
|
||||
font-size: 15px;
|
||||
|
||||
@@ -127,7 +127,9 @@ export const CollectionDetails: React.FC<{
|
||||
history.replace(
|
||||
`/collections/${result.payload.collection.id}/edit/details`,
|
||||
);
|
||||
history.push(`/collections/${result.payload.collection.id}`);
|
||||
history.push(`/collections/${result.payload.collection.id}`, {
|
||||
newCollection: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+41
-44
@@ -3,6 +3,7 @@ import { useCallback } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Button } from 'mastodon/components/button';
|
||||
import { ModalShell } from 'mastodon/components/modal_shell';
|
||||
|
||||
export interface BaseConfirmationModalProps {
|
||||
onClose: () => void;
|
||||
@@ -56,53 +57,49 @@ export const ConfirmationModal: React.FC<
|
||||
}, [onClose, onSecondary]);
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal safety-action-modal'>
|
||||
<div className='safety-action-modal__top'>
|
||||
<div className='safety-action-modal__confirmation'>
|
||||
<h1 id={titleId}>{title}</h1>
|
||||
{message && <p>{message}</p>}
|
||||
<ModalShell>
|
||||
<ModalShell.Body>
|
||||
<h1 id={titleId}>{title}</h1>
|
||||
{message && <p>{message}</p>}
|
||||
|
||||
{extraContent ?? children}
|
||||
</div>
|
||||
</div>
|
||||
{extraContent ?? children}
|
||||
</ModalShell.Body>
|
||||
|
||||
<div className='safety-action-modal__bottom'>
|
||||
<div className='safety-action-modal__actions'>
|
||||
<button onClick={onClose} className='link-button' type='button'>
|
||||
{cancel ?? (
|
||||
<FormattedMessage
|
||||
id='confirmation_modal.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{secondary && (
|
||||
<>
|
||||
<div className='spacer' />
|
||||
<button
|
||||
onClick={handleSecondary}
|
||||
className='link-button'
|
||||
type='button'
|
||||
disabled={disabled}
|
||||
>
|
||||
{secondary}
|
||||
</button>
|
||||
</>
|
||||
<ModalShell.Actions>
|
||||
<button onClick={onClose} className='link-button' type='button'>
|
||||
{cancel ?? (
|
||||
<FormattedMessage
|
||||
id='confirmation_modal.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* eslint-disable jsx-a11y/no-autofocus -- we are in a modal and thus autofocusing is justified */}
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
loading={updating}
|
||||
disabled={disabled}
|
||||
autoFocus={!noFocusButton}
|
||||
>
|
||||
{confirm}
|
||||
</Button>
|
||||
{/* eslint-enable */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{secondary && (
|
||||
<>
|
||||
<div className='spacer' />
|
||||
<button
|
||||
onClick={handleSecondary}
|
||||
className='link-button'
|
||||
type='button'
|
||||
disabled={disabled}
|
||||
>
|
||||
{secondary}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* eslint-disable jsx-a11y/no-autofocus -- we are in a modal and thus autofocusing is justified */}
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
loading={updating}
|
||||
disabled={disabled}
|
||||
autoFocus={!noFocusButton}
|
||||
>
|
||||
{confirm}
|
||||
</Button>
|
||||
{/* eslint-enable */}
|
||||
</ModalShell.Actions>
|
||||
</ModalShell>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,94 @@
|
||||
import type { FC, ReactNode } from 'react';
|
||||
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Button } from '@/mastodon/components/button';
|
||||
import { IconButton } from '@/mastodon/components/icon_button';
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
|
||||
export type { BaseConfirmationModalProps as DialogModalProps } from './confirmation_modals/confirmation_modal';
|
||||
|
||||
interface DialogModalProps {
|
||||
className?: string;
|
||||
title: ReactNode;
|
||||
onClose: () => void;
|
||||
description?: ReactNode;
|
||||
formClassName?: string;
|
||||
children?: ReactNode;
|
||||
noCancelButton?: boolean;
|
||||
onSave?: () => void;
|
||||
saveLabel?: ReactNode;
|
||||
}
|
||||
|
||||
export const DialogModal: FC<DialogModalProps> = ({
|
||||
className,
|
||||
title,
|
||||
onClose,
|
||||
description,
|
||||
formClassName,
|
||||
children,
|
||||
noCancelButton = false,
|
||||
onSave,
|
||||
saveLabel,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const showButtons = !noCancelButton || onSave;
|
||||
|
||||
return (
|
||||
<div className={classNames('modal-root__modal dialog-modal', className)}>
|
||||
<div className='dialog-modal__header'>
|
||||
<IconButton
|
||||
className='dialog-modal__header__close'
|
||||
title={intl.formatMessage({
|
||||
id: 'lightbox.close',
|
||||
defaultMessage: 'Close',
|
||||
})}
|
||||
icon='close'
|
||||
iconComponent={CloseIcon}
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
<h1 className='dialog-modal__header__title'>{title}</h1>
|
||||
</div>
|
||||
|
||||
<div className='dialog-modal__content'>
|
||||
{description && (
|
||||
<div className='dialog-modal__content__description'>
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={classNames('dialog-modal__content__form', formClassName)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showButtons && (
|
||||
<div className='dialog-modal__content__actions'>
|
||||
{!noCancelButton && (
|
||||
<Button onClick={onClose} secondary>
|
||||
<FormattedMessage
|
||||
id='confirmation_modal.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
{onSave && (
|
||||
<Button onClick={onClose}>
|
||||
{saveLabel ?? (
|
||||
<FormattedMessage
|
||||
id='confirmation_modal.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
DomainBlockModal,
|
||||
ReportModal,
|
||||
ReportCollectionModal,
|
||||
ShareCollectionModal,
|
||||
EmbedModal,
|
||||
ListAdder,
|
||||
CompareHistoryModal,
|
||||
@@ -79,6 +80,7 @@ export const MODAL_COMPONENTS = {
|
||||
'DOMAIN_BLOCK': DomainBlockModal,
|
||||
'REPORT': ReportModal,
|
||||
'REPORT_COLLECTION': ReportCollectionModal,
|
||||
'SHARE_COLLECTION': ShareCollectionModal,
|
||||
'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
|
||||
'EMBED': EmbedModal,
|
||||
'FOCAL_POINT': () => Promise.resolve({ default: AltTextModal }),
|
||||
@@ -95,6 +97,7 @@ export const MODAL_COMPONENTS = {
|
||||
'ACCOUNT_FIELD_OVERFLOW': () => import('@/mastodon/features/account_timeline/modals/field_modal').then(module => ({ default: module.AccountFieldModal })),
|
||||
'ACCOUNT_EDIT_NAME': () => import('@/mastodon/features/account_edit/components/name_modal').then(module => ({ default: module.NameModal })),
|
||||
'ACCOUNT_EDIT_BIO': () => import('@/mastodon/features/account_edit/components/bio_modal').then(module => ({ default: module.BioModal })),
|
||||
'ACCOUNT_EDIT_PROFILE_DISPLAY': () => import('@/mastodon/features/account_edit/components/profile_display_modal').then(module => ({ default: module.ProfileDisplayModal })),
|
||||
};
|
||||
|
||||
export default class ModalRoot extends PureComponent {
|
||||
|
||||
@@ -62,6 +62,12 @@ export function CollectionsEditor() {
|
||||
);
|
||||
}
|
||||
|
||||
export function ShareCollectionModal() {
|
||||
return import('../../collections/detail/share_modal').then(
|
||||
module => ({default: module.CollectionShareModal})
|
||||
);
|
||||
}
|
||||
|
||||
export function Status () {
|
||||
return import('../../status');
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user