Allow editing status quote policy (#35762)

This commit is contained in:
Echo 2025-08-14 17:04:32 +02:00 committed by GitHub
commit 651e51a82e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 591 additions and 16 deletions

View file

@ -0,0 +1,114 @@
import { useCallback, useId, useMemo, useRef, useState } from 'react';
import type { ComponentPropsWithoutRef, FC } from 'react';
import { FormattedMessage } from 'react-intl';
import type { MessageDescriptor } from 'react-intl';
import classNames from 'classnames';
import Overlay from 'react-overlays/Overlay';
import type { SelectItem } from '../dropdown_selector';
import { DropdownSelector } from '../dropdown_selector';
interface DropdownProps {
title: string;
disabled?: boolean;
items: SelectItem[];
onChange: (value: string) => void;
current: string;
emptyText?: MessageDescriptor;
classPrefix: string;
}
export const Dropdown: FC<
DropdownProps & Omit<ComponentPropsWithoutRef<'button'>, keyof DropdownProps>
> = ({
title,
disabled,
items,
current,
onChange,
classPrefix,
className,
...buttonProps
}) => {
const buttonRef = useRef<HTMLButtonElement>(null);
const accessibilityId = useId();
const [open, setOpen] = useState(false);
const handleToggle = useCallback(() => {
if (!disabled) {
setOpen((prevOpen) => !prevOpen);
}
}, [disabled]);
const handleClose = useCallback(() => {
setOpen(false);
}, []);
const currentText = useMemo(
() => items.find((i) => i.value === current)?.text,
[current, items],
);
return (
<>
<button
type='button'
{...buttonProps}
title={title}
aria-expanded={open}
aria-controls={accessibilityId}
onClick={handleToggle}
disabled={disabled}
className={classNames(
`${classPrefix}__button`,
{
active: open,
disabled,
},
className,
)}
ref={buttonRef}
>
{currentText ?? (
<FormattedMessage
id='dropdown.empty'
defaultMessage='Select an option'
/>
)}
</button>
<Overlay
show={open}
offset={[0, 4]}
placement='bottom-start'
onHide={handleClose}
flip
target={buttonRef.current}
popperConfig={{
strategy: 'fixed',
}}
>
{({ props, placement }) => (
<div {...props} className={`${classPrefix}__overlay`}>
<div
className={classNames(
'dropdown-animation',
`${classPrefix}__dropdown`,
placement,
)}
id={accessibilityId}
>
<DropdownSelector
items={items}
value={current}
onClose={handleClose}
onChange={onChange}
classNamePrefix={classPrefix}
/>
</div>
</div>
)}
</Overlay>
</>
);
};

View file

@ -13,8 +13,8 @@ const listenerOptions = supportsPassiveEvents
? { passive: true, capture: true }
: true;
export interface SelectItem {
value: string;
export interface SelectItem<Value extends string = string> {
value: Value;
icon?: string;
iconComponent?: IconProp;
text: string;
@ -24,7 +24,7 @@ export interface SelectItem {
interface Props {
value: string;
classNamePrefix: string;
classNamePrefix?: string;
style?: React.CSSProperties;
items: SelectItem[];
onChange: (value: string) => void;

View file

@ -29,6 +29,7 @@ import { Dropdown } from 'mastodon/components/dropdown_menu';
import { me } from '../initial_state';
import { IconButton } from './icon_button';
import { isFeatureEnabled } from '../utils/environment';
const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' },
@ -68,6 +69,7 @@ const messages = defineMessages({
filter: { id: 'status.filter', defaultMessage: 'Filter this post' },
openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
revokeQuote: { id: 'status.revoke_quote', defaultMessage: 'Remove my post from @{name}s post' },
quotePolicyChange: { id: 'status.quote_policy_change', defaultMessage: 'Change who can quote' },
});
const mapStateToProps = (state, { status }) => {
@ -89,6 +91,7 @@ class StatusActionBar extends ImmutablePureComponent {
onReblog: PropTypes.func,
onDelete: PropTypes.func,
onRevokeQuote: PropTypes.func,
onQuotePolicyChange: PropTypes.func,
onDirect: PropTypes.func,
onMention: PropTypes.func,
onMute: PropTypes.func,
@ -200,7 +203,11 @@ class StatusActionBar extends ImmutablePureComponent {
handleRevokeQuoteClick = () => {
this.props.onRevokeQuote(this.props.status);
}
};
handleQuotePolicyChange = () => {
this.props.onQuotePolicyChange(this.props.status);
};
handleBlockClick = () => {
const { status, relationship, onBlock, onUnblock } = this.props;
@ -291,6 +298,9 @@ class StatusActionBar extends ImmutablePureComponent {
if (writtenByMe || withDismiss) {
menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
if (writtenByMe && isFeatureEnabled('outgoing_quotes') && !['private', 'direct'].includes(status.get('visibility'))) {
menu.push({ text: intl.formatMessage(messages.quotePolicyChange), action: this.handleQuotePolicyChange });
}
menu.push(null);
}