Merge commit 'eb118d85238da8d0583868abab2756bb329742ba' into glitch-soc/merge-upstream
Conflicts: - `yarn.lock`: Upstream updated dependencies textually adjacent to glitch-soc-only ones. Updated them as upstream did.
This commit is contained in:
commit
06f45a2046
219 files changed
+2373
-1267
No files matched your search
@@ -18,7 +18,7 @@ export const messages = defineMessages({
|
||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
public_long: { id: 'privacy.public.long', defaultMessage: 'Anyone on and off Mastodon' },
|
||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Quiet public' },
|
||||
unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Fewer algorithmic fanfares' },
|
||||
unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Hidden from Mastodon search results, trending, and public timelines' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers' },
|
||||
private_long: { id: 'privacy.private.long', defaultMessage: 'Only your followers' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Specific people' },
|
||||
|
||||
@@ -79,10 +79,12 @@ const visibilityOptions = {
|
||||
const PrivacyModalButton: FC<PrivacyDropdownProps> = ({ disabled = false }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const { visibility, quotePolicy } = useAppSelector((state) => ({
|
||||
visibility: state.compose.get('privacy') as StatusVisibility,
|
||||
quotePolicy: state.compose.get('quote_policy') as ApiQuotePolicy,
|
||||
}));
|
||||
const quotePolicy = useAppSelector(
|
||||
(state) => state.compose.get('quote_policy') as ApiQuotePolicy,
|
||||
);
|
||||
const visibility = useAppSelector(
|
||||
(state) => state.compose.get('privacy') as StatusVisibility,
|
||||
);
|
||||
|
||||
const { icon, iconComponent } = useMemo(() => {
|
||||
const option = visibilityOptions[visibility];
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { ComponentPropsWithoutRef, ElementType } from 'react';
|
||||
|
||||
import { isModernEmojiEnabled } from '@/mastodon/utils/environment';
|
||||
|
||||
import { useEmojify } from './hooks';
|
||||
import type { CustomEmojiMapArg } from './types';
|
||||
|
||||
@@ -13,7 +15,7 @@ type EmojiHTMLProps<Element extends ElementType = 'div'> = Omit<
|
||||
shallow?: boolean;
|
||||
};
|
||||
|
||||
export const EmojiHTML = ({
|
||||
export const ModernEmojiHTML = ({
|
||||
extraEmojis,
|
||||
htmlString,
|
||||
as: Wrapper = 'div', // Rename for syntax highlighting
|
||||
@@ -34,3 +36,14 @@ export const EmojiHTML = ({
|
||||
<Wrapper {...props} dangerouslySetInnerHTML={{ __html: emojifiedHtml }} />
|
||||
);
|
||||
};
|
||||
|
||||
export const EmojiHTML = <Element extends ElementType>(
|
||||
props: EmojiHTMLProps<Element>,
|
||||
) => {
|
||||
if (isModernEmojiEnabled()) {
|
||||
return <ModernEmojiHTML {...props} />;
|
||||
}
|
||||
const { as: asElement, htmlString, extraEmojis, ...rest } = props;
|
||||
const Wrapper = asElement ?? 'div';
|
||||
return <Wrapper {...rest} dangerouslySetInnerHTML={{ __html: htmlString }} />;
|
||||
};
|
||||
@@ -143,16 +143,14 @@ export const SelectWithLabel: React.FC<PropsWithChildren<Props>> = ({
|
||||
</div>
|
||||
|
||||
<div className='app-form__toggle__toggle'>
|
||||
<div>
|
||||
<Dropdown
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
aria-labelledby={labelId}
|
||||
aria-describedby={descId}
|
||||
options={options}
|
||||
/>
|
||||
</div>
|
||||
<Dropdown
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
aria-labelledby={labelId}
|
||||
aria-describedby={descId}
|
||||
options={options}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
);
|
||||
|
||||
@@ -20,7 +20,7 @@ import { IconButton } from '../../../components/icon_button';
|
||||
import { Dropdown } from 'mastodon/components/dropdown_menu';
|
||||
import { me } from '../../../initial_state';
|
||||
import { isFeatureEnabled } from '@/mastodon/utils/environment';
|
||||
import { ReblogButton } from '@/mastodon/components/status/reblog_button';
|
||||
import { BoostButton } from '@/mastodon/components/status/boost_button';
|
||||
|
||||
const messages = defineMessages({
|
||||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||
@@ -310,7 +310,7 @@ class ActionBar extends PureComponent {
|
||||
<div className='detailed-status__action-bar'>
|
||||
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} iconComponent={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} /></div>
|
||||
<div className='detailed-status__button'>
|
||||
<ReblogButton status={status} />
|
||||
<BoostButton status={status} />
|
||||
</div>
|
||||
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={favouriteTitle} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className='bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={bookmarkTitle} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} /></div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { DropdownMenuItemContent } from 'mastodon/components/dropdown_menu';
|
||||
import type { MenuItem } from 'mastodon/models/dropdown_menu';
|
||||
import {
|
||||
isActionItem,
|
||||
@@ -18,14 +19,14 @@ export const ActionsModal: React.FC<{
|
||||
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
|
||||
}
|
||||
|
||||
const { text, dangerous } = option;
|
||||
const { text, highlighted, disabled, dangerous } = option;
|
||||
|
||||
let element: React.ReactElement;
|
||||
|
||||
if (isActionItem(option)) {
|
||||
element = (
|
||||
<button onClick={onClick} data-index={i}>
|
||||
{text}
|
||||
<button onClick={onClick} data-index={i} disabled={disabled}>
|
||||
<DropdownMenuItemContent item={option} />
|
||||
</button>
|
||||
);
|
||||
} else if (isExternalLinkItem(option)) {
|
||||
@@ -38,21 +39,22 @@ export const ActionsModal: React.FC<{
|
||||
onClick={onClick}
|
||||
data-index={i}
|
||||
>
|
||||
{text}
|
||||
<DropdownMenuItemContent item={option} />
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
element = (
|
||||
<Link to={option.to} onClick={onClick} data-index={i}>
|
||||
{text}
|
||||
<DropdownMenuItemContent item={option} />
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li
|
||||
className={classNames({
|
||||
className={classNames('dropdown-menu__item', {
|
||||
'dropdown-menu__item--dangerous': dangerous,
|
||||
'dropdown-menu__item--highlighted': highlighted,
|
||||
})}
|
||||
key={`${text}-${i}`}
|
||||
>
|
||||
|
||||
+1
-5
@@ -43,10 +43,6 @@ export const ConfirmationModal: React.FC<
|
||||
onSecondary?.();
|
||||
}, [onClose, onSecondary]);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
onClose();
|
||||
}, [onClose]);
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal safety-action-modal'>
|
||||
<div className='safety-action-modal__top'>
|
||||
@@ -58,7 +54,7 @@ export const ConfirmationModal: React.FC<
|
||||
|
||||
<div className='safety-action-modal__bottom'>
|
||||
<div className='safety-action-modal__actions'>
|
||||
<button onClick={handleCancel} className='link-button'>
|
||||
<button onClick={onClose} className='link-button'>
|
||||
{cancel ?? (
|
||||
<FormattedMessage
|
||||
id='confirmation_modal.cancel'
|
||||
|
||||
@@ -11,3 +11,4 @@ export { ConfirmLogOutModal } from './log_out';
|
||||
export { ConfirmFollowToListModal } from './follow_to_list';
|
||||
export { ConfirmMissingAltTextModal } from './missing_alt_text';
|
||||
export { ConfirmRevokeQuoteModal } from './revoke_quote';
|
||||
export { QuietPostQuoteInfoModal } from './quiet_post_quote_info';
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { quoteCompose } from '@/mastodon/actions/compose_typed';
|
||||
import { closeModal } from '@/mastodon/actions/modal';
|
||||
import { changeSetting } from '@/mastodon/actions/settings';
|
||||
import type { Status } from '@/mastodon/models/status';
|
||||
import { useAppDispatch } from '@/mastodon/store';
|
||||
|
||||
import { ConfirmationModal } from './confirmation_modal';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'confirmations.quiet_post_quote_info.title',
|
||||
defaultMessage: 'Quoting quiet public posts',
|
||||
},
|
||||
message: {
|
||||
id: 'confirmations.quiet_post_quote_info.message',
|
||||
defaultMessage:
|
||||
'When quoting a quiet public post, your post will be hidden from trending timelines.',
|
||||
},
|
||||
got_it: {
|
||||
id: 'confirmations.quiet_post_quote_info.got_it',
|
||||
defaultMessage: 'Got it',
|
||||
},
|
||||
dismiss: {
|
||||
id: 'confirmations.quiet_post_quote_info.dismiss',
|
||||
defaultMessage: "Don't remind me again",
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* [1] Since we only want this modal to have two buttons – "Don't ask again" and
|
||||
* "Got it" – , we have to use the `onClose` handler to handle the "Don't ask again"
|
||||
* functionality. Because of this, we need to set `closeWhenConfirm` to false and
|
||||
* close the modal manually.
|
||||
* This prevents the modal from being dismissed permanently when just confirming.
|
||||
*/
|
||||
|
||||
export const QuietPostQuoteInfoModal: React.FC<{ status: Status }> = ({
|
||||
status,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const confirm = useCallback(() => {
|
||||
dispatch(quoteCompose(status));
|
||||
// [1]
|
||||
dispatch(
|
||||
closeModal({ modalType: 'CONFIRM_QUIET_QUOTE', ignoreFocus: true }),
|
||||
);
|
||||
}, [dispatch, status]);
|
||||
|
||||
const dismiss = useCallback(() => {
|
||||
dispatch(quoteCompose(status));
|
||||
dispatch(
|
||||
changeSetting(['dismissed_banners', 'quote/quiet_post_hint'], true),
|
||||
);
|
||||
// [1]
|
||||
dispatch(
|
||||
closeModal({ modalType: 'CONFIRM_QUIET_QUOTE', ignoreFocus: true }),
|
||||
);
|
||||
}, [dispatch, status]);
|
||||
|
||||
return (
|
||||
<ConfirmationModal
|
||||
closeWhenConfirm={false} // [1]
|
||||
title={intl.formatMessage(messages.title)}
|
||||
message={intl.formatMessage(messages.message)}
|
||||
confirm={intl.formatMessage(messages.got_it)}
|
||||
cancel={intl.formatMessage(messages.dismiss)}
|
||||
onConfirm={confirm}
|
||||
onClose={dismiss}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -201,8 +201,6 @@ class MediaModal extends ImmutablePureComponent {
|
||||
preview={image.get('preview_url')}
|
||||
blurhash={image.get('blurhash')}
|
||||
src={image.get('url')}
|
||||
width={image.get('width')}
|
||||
height={image.get('height')}
|
||||
frameRate={image.getIn(['meta', 'original', 'frame_rate'])}
|
||||
aspectRatio={`${image.getIn(['meta', 'original', 'width'])} / ${image.getIn(['meta', 'original', 'height'])}`}
|
||||
startTime={currentTime || 0}
|
||||
@@ -219,8 +217,6 @@ class MediaModal extends ImmutablePureComponent {
|
||||
return (
|
||||
<GIFV
|
||||
src={image.get('url')}
|
||||
width={width}
|
||||
height={height}
|
||||
key={image.get('url')}
|
||||
alt={description}
|
||||
lang={lang}
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
ConfirmFollowToListModal,
|
||||
ConfirmMissingAltTextModal,
|
||||
ConfirmRevokeQuoteModal,
|
||||
QuietPostQuoteInfoModal,
|
||||
} from './confirmation_modals';
|
||||
import { ImageModal } from './image_modal';
|
||||
import MediaModal from './media_modal';
|
||||
@@ -62,6 +63,7 @@ export const MODAL_COMPONENTS = {
|
||||
'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }),
|
||||
'CONFIRM_MISSING_ALT_TEXT': () => Promise.resolve({ default: ConfirmMissingAltTextModal }),
|
||||
'CONFIRM_REVOKE_QUOTE': () => Promise.resolve({ default: ConfirmRevokeQuoteModal }),
|
||||
'CONFIRM_QUIET_QUOTE': () => Promise.resolve({ default: QuietPostQuoteInfoModal }),
|
||||
'MUTE': MuteModal,
|
||||
'BLOCK': BlockModal,
|
||||
'DOMAIN_BLOCK': DomainBlockModal,
|
||||
|
||||
@@ -163,7 +163,6 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||
value: 'unlisted',
|
||||
text: intl.formatMessage(privacyMessages.unlisted_short),
|
||||
meta: intl.formatMessage(privacyMessages.unlisted_long),
|
||||
extra: intl.formatMessage(privacyMessages.unlisted_extra),
|
||||
icon: 'unlock',
|
||||
iconComponent: QuietTimeIcon,
|
||||
},
|
||||
@@ -199,8 +198,11 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||
onClose();
|
||||
}, [onChange, onClose, visibility, quotePolicy]);
|
||||
|
||||
const privacyDropdownId = useId();
|
||||
const quoteDropdownId = useId();
|
||||
const uniqueId = useId();
|
||||
const visibilityLabelId = `${uniqueId}-visibility-label`;
|
||||
const visibilityDescriptionId = `${uniqueId}-visibility-desc`;
|
||||
const quoteLabelId = `${uniqueId}-quote-label`;
|
||||
const quoteDescriptionId = `${uniqueId}-quote-desc`;
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal dialog-modal visibility-modal'>
|
||||
@@ -235,28 +237,36 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||
/>
|
||||
</div>
|
||||
<div className='dialog-modal__content__form'>
|
||||
<label
|
||||
htmlFor={privacyDropdownId}
|
||||
className={classNames('visibility-dropdown__label', {
|
||||
<div
|
||||
className={classNames('visibility-dropdown', {
|
||||
disabled: disableVisibility,
|
||||
})}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='visibility_modal.privacy_label'
|
||||
defaultMessage='Visibility'
|
||||
/>
|
||||
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||
<label
|
||||
className='visibility-dropdown__label'
|
||||
id={visibilityLabelId}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='visibility_modal.privacy_label'
|
||||
defaultMessage='Visibility'
|
||||
/>
|
||||
</label>
|
||||
|
||||
<Dropdown
|
||||
items={visibilityItems}
|
||||
classPrefix='visibility-dropdown'
|
||||
current={visibility}
|
||||
onChange={handleVisibilityChange}
|
||||
title={intl.formatMessage(privacyMessages.change_privacy)}
|
||||
labelId={visibilityLabelId}
|
||||
descriptionId={visibilityDescriptionId}
|
||||
classPrefix='visibility-dropdown'
|
||||
disabled={disableVisibility}
|
||||
id={privacyDropdownId}
|
||||
/>
|
||||
{!!statusId && (
|
||||
<p className='visibility-dropdown__helper'>
|
||||
<p
|
||||
className='visibility-dropdown__helper'
|
||||
id='visibilityDescriptionId'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='visibility_modal.helper.privacy_editing'
|
||||
defaultMessage="Visibility can't be changed after a post is published."
|
||||
@@ -264,37 +274,47 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||
</p>
|
||||
)}
|
||||
{!statusId && disablePublicVisibilities && (
|
||||
<p className='visibility-dropdown__helper'>
|
||||
<p
|
||||
className='visibility-dropdown__helper'
|
||||
id='visibilityDescriptionId'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='visibility_modal.helper.privacy_private_self_quote'
|
||||
defaultMessage='Self-quotes of private posts cannot be made public.'
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label
|
||||
htmlFor={quoteDropdownId}
|
||||
className={classNames('visibility-dropdown__label', {
|
||||
<div
|
||||
className={classNames('visibility-dropdown', {
|
||||
disabled: disableQuotePolicy,
|
||||
})}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='visibility_modal.quote_label'
|
||||
defaultMessage='Who can quote'
|
||||
/>
|
||||
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||
<label className='visibility-dropdown__label' id={quoteLabelId}>
|
||||
<FormattedMessage
|
||||
id='visibility_modal.quote_label'
|
||||
defaultMessage='Who can quote'
|
||||
/>
|
||||
</label>
|
||||
|
||||
<Dropdown
|
||||
items={quoteItems}
|
||||
onChange={handleQuotePolicyChange}
|
||||
classPrefix='visibility-dropdown'
|
||||
current={disableQuotePolicy ? 'nobody' : quotePolicy}
|
||||
title={intl.formatMessage(messages.buttonTitle)}
|
||||
onChange={handleQuotePolicyChange}
|
||||
labelId={quoteLabelId}
|
||||
descriptionId={quoteDescriptionId}
|
||||
classPrefix='visibility-dropdown'
|
||||
disabled={disableQuotePolicy}
|
||||
id={quoteDropdownId}
|
||||
/>
|
||||
<QuotePolicyHelper policy={quotePolicy} visibility={visibility} />
|
||||
</label>
|
||||
<QuotePolicyHelper
|
||||
policy={quotePolicy}
|
||||
visibility={visibility}
|
||||
className='visibility-dropdown__helper'
|
||||
id={quoteDescriptionId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='dialog-modal__content__actions'>
|
||||
<Button onClick={onClose} secondary>
|
||||
@@ -317,42 +337,44 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||
);
|
||||
VisibilityModal.displayName = 'VisibilityModal';
|
||||
|
||||
const QuotePolicyHelper: FC<{
|
||||
policy: ApiQuotePolicy;
|
||||
visibility: StatusVisibility;
|
||||
}> = ({ policy, visibility }) => {
|
||||
const QuotePolicyHelper: FC<
|
||||
{
|
||||
policy: ApiQuotePolicy;
|
||||
visibility: StatusVisibility;
|
||||
} & React.ComponentPropsWithoutRef<'p'>
|
||||
> = ({ policy, visibility, ...otherProps }) => {
|
||||
let hintText: React.ReactElement | undefined;
|
||||
|
||||
if (visibility === 'unlisted' && policy !== 'nobody') {
|
||||
return (
|
||||
<p className='visibility-dropdown__helper'>
|
||||
<FormattedMessage
|
||||
id='visibility_modal.helper.unlisted_quoting'
|
||||
defaultMessage='When people quote you, their post will also be hidden from trending timelines.'
|
||||
/>
|
||||
</p>
|
||||
hintText = (
|
||||
<FormattedMessage
|
||||
id='visibility_modal.helper.unlisted_quoting'
|
||||
defaultMessage='When people quote you, their post will also be hidden from trending timelines.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (visibility === 'private') {
|
||||
return (
|
||||
<p className='visibility-dropdown__helper'>
|
||||
<FormattedMessage
|
||||
id='visibility_modal.helper.private_quoting'
|
||||
defaultMessage="Follower-only posts authored on Mastodon can't be quoted by others."
|
||||
/>
|
||||
</p>
|
||||
hintText = (
|
||||
<FormattedMessage
|
||||
id='visibility_modal.helper.private_quoting'
|
||||
defaultMessage="Follower-only posts authored on Mastodon can't be quoted by others."
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (visibility === 'direct') {
|
||||
return (
|
||||
<p className='visibility-dropdown__helper'>
|
||||
<FormattedMessage
|
||||
id='visibility_modal.helper.direct_quoting'
|
||||
defaultMessage="Private mentions authored on Mastodon can't be quoted by others."
|
||||
/>
|
||||
</p>
|
||||
hintText = (
|
||||
<FormattedMessage
|
||||
id='visibility_modal.helper.direct_quoting'
|
||||
defaultMessage="Private mentions authored on Mastodon can't be quoted by others."
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
if (!hintText) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <p {...otherProps}>{hintText}</p>;
|
||||
};
|
||||
Reference in new issue
Block a user