Add support for FEP-3b86 (Activity Intents) (#38120)

This commit is contained in:
Eugen Rochko authored and GitHub committed 2026-03-10 10:21:24 +00:00
1 parent 7d58ce309c
commit 69b1f60f4e
10 files changed
+119 -42

No files matched your search

@@ -92,6 +92,7 @@ export const FollowButton: React.FC<{
openModal({
modalType: 'INTERACTION',
modalProps: {
intent: 'follow',
accountId: accountId,
url: account?.url,
},
@@ -110,6 +110,7 @@ export const Poll: React.FC<PollProps> = ({ pollId, disabled, status }) => {
openModal({
modalType: 'INTERACTION',
modalProps: {
intent: 'vote',
accountId: status.getIn(['account', 'id']),
url: status.get('uri'),
},
@@ -47,6 +47,7 @@ const StandaloneBoostButton: FC<ReblogButtonProps> = ({ status, counters }) => {
openModal({
modalType: 'INTERACTION',
modalProps: {
intent: 'reblog',
accountId: status.getIn(['account', 'id']),
url: status.get('uri'),
},
@@ -120,6 +121,7 @@ const BoostOrQuoteMenu: FC<ReblogButtonProps> = ({ status, counters }) => {
openModal({
modalType: 'INTERACTION',
modalProps: {
intent: 'reblog',
accountId: status.getIn(['account', 'id']),
url: status.get('uri'),
},
@@ -124,7 +124,7 @@ class StatusActionBar extends ImmutablePureComponent {
if (signedIn) {
this.props.onReply(this.props.status);
} else {
this.props.onInteractionModal(this.props.status);
this.props.onInteractionModal(this.props.status, 'reply');
}
};
@@ -146,7 +146,7 @@ class StatusActionBar extends ImmutablePureComponent {
if (signedIn) {
this.props.onFavourite(this.props.status);
} else {
this.props.onInteractionModal(this.props.status);
this.props.onInteractionModal(this.props.status, 'favourite');
}
};
@@ -382,7 +382,7 @@ class StatusActionBar extends ImmutablePureComponent {
const bookmarkTitle = intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark);
const favouriteTitle = intl.formatMessage(status.get('favourited') ? messages.removeFavourite : messages.favourite);
const isReply = status.get('in_reply_to_account_id') === status.getIn(['account', 'id']);
const shouldShowQuoteRemovalHint = isQuotingMe && contextType === 'notifications';
return (
@@ -77,7 +77,7 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({
onReblog (status, e) {
dispatch(toggleReblog(status.get('id'), e.shiftKey));
},
onQuote (status) {
dispatch(quoteComposeById(status.get('id')));
},
@@ -231,10 +231,11 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({
dispatch(deployPictureInPicture({statusId: status.get('id'), accountId: status.getIn(['account', 'id']), playerType: type, props: mediaProps}));
},
onInteractionModal (status) {
onInteractionModal (status, intent) {
dispatch(openModal({
modalType: 'INTERACTION',
modalProps: {
intent,
accountId: status.getIn(['account', 'id']),
url: status.get('uri'),
},
@@ -25,6 +25,8 @@ const messages = defineMessages({
},
});
type InteractionIntent = 'follow' | 'reblog' | 'favourite' | 'reply' | 'vote';
interface LoginFormMessage {
type:
| 'fetchInteractionURL'
@@ -32,6 +34,8 @@ interface LoginFormMessage {
| 'fetchInteractionURL-success';
uri_or_domain: string;
template?: string;
param?: string;
intent?: InteractionIntent;
}
const PERSISTENCE_KEY = 'mastodon_home';
@@ -110,7 +114,11 @@ const isValueValid = (value: string) => {
}
};
const sendToFrame = (frame: HTMLIFrameElement | null, value: string): void => {
const sendToFrame = (
frame: HTMLIFrameElement | null,
value: string,
intent: string,
): void => {
if (valueToDomain(value.trim()) === localDomain) {
window.location.href = '/auth/sign_in';
return;
@@ -120,6 +128,7 @@ const sendToFrame = (frame: HTMLIFrameElement | null, value: string): void => {
{
type: 'fetchInteractionURL',
uri_or_domain: value.trim(),
intent,
},
window.origin,
);
@@ -127,7 +136,8 @@ const sendToFrame = (frame: HTMLIFrameElement | null, value: string): void => {
const LoginForm: React.FC<{
resourceUrl: string;
}> = ({ resourceUrl }) => {
intent: string;
}> = ({ resourceUrl, intent }) => {
const intl = useIntl();
const [value, setValue] = useState(
localStorage.getItem(PERSISTENCE_KEY) ?? '',
@@ -161,7 +171,7 @@ const LoginForm: React.FC<{
try {
const url = new URL(
event.data.template.replace(
'{uri}',
`{${event.data.param}}`,
encodeURIComponent(resourceUrl),
),
);
@@ -242,8 +252,8 @@ const LoginForm: React.FC<{
const handleSubmit = useCallback(() => {
setIsSubmitting(true);
sendToFrame(iframeRef.current, value);
}, [setIsSubmitting, value]);
sendToFrame(iframeRef.current, value, intent);
}, [setIsSubmitting, value, intent]);
const handleFocus = useCallback(() => {
setExpanded(true);
@@ -287,7 +297,7 @@ const LoginForm: React.FC<{
setError(false);
setValue(selectedOptionValue);
setIsSubmitting(true);
sendToFrame(iframeRef.current, selectedOptionValue);
sendToFrame(iframeRef.current, selectedOptionValue, intent);
}
break;
@@ -300,6 +310,7 @@ const LoginForm: React.FC<{
setValue,
selectedOption,
options,
intent,
],
);
@@ -318,9 +329,9 @@ const LoginForm: React.FC<{
setValue(option);
setError(false);
setIsSubmitting(true);
sendToFrame(iframeRef.current, option);
sendToFrame(iframeRef.current, option, intent);
},
[options, setSelectedOption, setValue, setError],
[options, setSelectedOption, setValue, setError, intent],
);
const domain = (valueToDomain(value) ?? '').trim();
@@ -404,7 +415,8 @@ const LoginForm: React.FC<{
const InteractionModal: React.FC<{
accountId: string;
url: string;
}> = ({ accountId, url }) => {
intent: string;
}> = ({ accountId, url, intent }) => {
const dispatch = useAppDispatch();
const signupUrl = useAppSelector(
(state) =>
@@ -479,7 +491,7 @@ const InteractionModal: React.FC<{
</p>
</div>
<LoginForm resourceUrl={url} />
<LoginForm resourceUrl={url} intent={intent} />
<p>
<FormattedMessage
@@ -86,6 +86,7 @@ export const Footer: React.FC<{
openModal({
modalType: 'INTERACTION',
modalProps: {
intent: 'reply',
accountId: status.getIn(['account', 'id']),
url: status.get('uri'),
},
@@ -106,6 +107,7 @@ export const Footer: React.FC<{
openModal({
modalType: 'INTERACTION',
modalProps: {
intent: 'favourite',
accountId: status.getIn(['account', 'id']),
url: status.get('uri'),
},
@@ -190,6 +190,7 @@ class Status extends ImmutablePureComponent {
dispatch(openModal({
modalType: 'INTERACTION',
modalProps: {
intent: 'favourite',
accountId: status.getIn(['account', 'id']),
url: status.get('uri'),
},
@@ -219,6 +220,7 @@ class Status extends ImmutablePureComponent {
dispatch(openModal({
modalType: 'INTERACTION',
modalProps: {
intent: 'reply',
accountId: status.getIn(['account', 'id']),
url: status.get('uri'),
},
@@ -236,6 +238,7 @@ class Status extends ImmutablePureComponent {
dispatch(openModal({
modalType: 'INTERACTION',
modalProps: {
intent: 'reblog',
accountId: status.getIn(['account', 'id']),
url: status.get('uri'),
},
@@ -274,13 +277,13 @@ class Status extends ImmutablePureComponent {
// Error handling - could show error message
});
} else {
dispatch(openModal({
modalType: 'CONFIRM_DELETE_STATUS',
modalProps: {
statusId: status.get('id'),
dispatch(openModal({
modalType: 'CONFIRM_DELETE_STATUS',
modalProps: {
statusId: status.get('id'),
withRedraft,
onDeleteSuccess: handleDeleteSuccess
}
}
}));
}
};
@@ -498,7 +501,7 @@ class Status extends ImmutablePureComponent {
// Only highlight replies after the initial load
if (prevProps.descendantsIds.length && isSameStatus) {
const newRepliesIds = difference(descendantsIds, prevProps.descendantsIds);
if (newRepliesIds.length) {
this.setState({newRepliesIds});
}
@@ -631,7 +634,7 @@ class Status extends ImmutablePureComponent {
</Hotkeys>
{descendants}
<RefreshController
isLocal={isLocal}
statusId={status.get('id')}