sforkowany z mirror/soapbox
StatusActionBar: move action code directly into component, clean up
rodzic
4970c6c307
commit
33fbb0f147
|
@ -102,7 +102,6 @@ const Status: React.FC<IStatus> = (props) => {
|
||||||
const node = useRef<HTMLDivElement>(null);
|
const node = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const [showMedia, setShowMedia] = useState<boolean>(defaultMediaVisibility(status, displayMedia));
|
const [showMedia, setShowMedia] = useState<boolean>(defaultMediaVisibility(status, displayMedia));
|
||||||
const [emojiSelectorFocused, setEmojiSelectorFocused] = useState(false);
|
|
||||||
|
|
||||||
const actualStatus = getActualStatus(status);
|
const actualStatus = getActualStatus(status);
|
||||||
|
|
||||||
|
@ -192,12 +191,7 @@ const Status: React.FC<IStatus> = (props) => {
|
||||||
_expandEmojiSelector();
|
_expandEmojiSelector();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEmojiSelectorUnfocus = (): void => {
|
|
||||||
setEmojiSelectorFocused(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const _expandEmojiSelector = (): void => {
|
const _expandEmojiSelector = (): void => {
|
||||||
setEmojiSelectorFocused(true);
|
|
||||||
const firstEmoji: HTMLDivElement | null | undefined = node.current?.querySelector('.emoji-react-selector .emoji-react-selector__emoji');
|
const firstEmoji: HTMLDivElement | null | undefined = node.current?.querySelector('.emoji-react-selector .emoji-react-selector__emoji');
|
||||||
firstEmoji?.focus();
|
firstEmoji?.focus();
|
||||||
};
|
};
|
||||||
|
@ -397,13 +391,7 @@ const Status: React.FC<IStatus> = (props) => {
|
||||||
{quote}
|
{quote}
|
||||||
|
|
||||||
{!hideActionBar && (
|
{!hideActionBar && (
|
||||||
// @ts-ignore
|
<StatusActionBar status={actualStatus} />
|
||||||
<StatusActionBar
|
|
||||||
emojiSelectorFocused={emojiSelectorFocused}
|
|
||||||
handleEmojiSelectorUnfocus={handleEmojiSelectorUnfocus}
|
|
||||||
{...props}
|
|
||||||
status={actualStatus}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,19 +1,26 @@
|
||||||
import { List as ImmutableList } from 'immutable';
|
import { List as ImmutableList } from 'immutable';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { blockAccount } from 'soapbox/actions/accounts';
|
||||||
|
import { showAlertForError } from 'soapbox/actions/alerts';
|
||||||
|
import { launchChat } from 'soapbox/actions/chats';
|
||||||
|
import { directCompose, mentionCompose, quoteCompose, replyCompose } from 'soapbox/actions/compose';
|
||||||
|
import { bookmark, favourite, pin, reblog, unbookmark, unfavourite, unpin, unreblog } from 'soapbox/actions/interactions';
|
||||||
import { openModal } from 'soapbox/actions/modals';
|
import { openModal } from 'soapbox/actions/modals';
|
||||||
|
import { deactivateUserModal, deleteStatusModal, deleteUserModal, toggleStatusSensitivityModal } from 'soapbox/actions/moderation';
|
||||||
|
import { initMuteModal } from 'soapbox/actions/mutes';
|
||||||
|
import { initReport } from 'soapbox/actions/reports';
|
||||||
|
import { deleteStatus, editStatus, muteStatus, unmuteStatus } from 'soapbox/actions/statuses';
|
||||||
import EmojiButtonWrapper from 'soapbox/components/emoji-button-wrapper';
|
import EmojiButtonWrapper from 'soapbox/components/emoji-button-wrapper';
|
||||||
import StatusActionButton from 'soapbox/components/status-action-button';
|
import StatusActionButton from 'soapbox/components/status-action-button';
|
||||||
import DropdownMenuContainer from 'soapbox/containers/dropdown_menu_container';
|
import DropdownMenuContainer from 'soapbox/containers/dropdown_menu_container';
|
||||||
import { useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks';
|
import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount, useSettings, useSoapboxConfig } from 'soapbox/hooks';
|
||||||
import { getReactForStatus, reduceEmoji } from 'soapbox/utils/emoji_reacts';
|
import { getReactForStatus, reduceEmoji } from 'soapbox/utils/emoji_reacts';
|
||||||
|
|
||||||
import type { History } from 'history';
|
|
||||||
import type { Menu } from 'soapbox/components/dropdown_menu';
|
import type { Menu } from 'soapbox/components/dropdown_menu';
|
||||||
import type { Status } from 'soapbox/types/entities';
|
import type { Account, Status } from 'soapbox/types/entities';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||||
|
@ -59,75 +66,43 @@ const messages = defineMessages({
|
||||||
reactionCry: { id: 'status.reactions.cry', defaultMessage: 'Sad' },
|
reactionCry: { id: 'status.reactions.cry', defaultMessage: 'Sad' },
|
||||||
reactionWeary: { id: 'status.reactions.weary', defaultMessage: 'Weary' },
|
reactionWeary: { id: 'status.reactions.weary', defaultMessage: 'Weary' },
|
||||||
quotePost: { id: 'status.quote', defaultMessage: 'Quote post' },
|
quotePost: { id: 'status.quote', defaultMessage: 'Quote post' },
|
||||||
|
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||||
|
deleteHeading: { id: 'confirmations.delete.heading', defaultMessage: 'Delete post' },
|
||||||
|
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this post?' },
|
||||||
|
redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
|
||||||
|
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this post and re-draft it? Favorites and reposts will be lost, and replies to the original post will be orphaned.' },
|
||||||
|
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
|
||||||
|
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
||||||
|
redraftHeading: { id: 'confirmations.redraft.heading', defaultMessage: 'Delete & redraft' },
|
||||||
|
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||||
|
blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IStatusActionBar {
|
interface IStatusActionBar {
|
||||||
status: Status,
|
status: Status,
|
||||||
onReply: (status: Status) => void,
|
|
||||||
onFavourite: (status: Status) => void,
|
|
||||||
onEmojiReact: (status: Status, emoji: string) => void,
|
|
||||||
onBookmark: (status: Status) => void,
|
|
||||||
onReblog: (status: Status, e: React.MouseEvent) => void,
|
|
||||||
onQuote: (status: Status) => void,
|
|
||||||
onDelete: (status: Status, redraft?: boolean) => void,
|
|
||||||
onEdit: (status: Status) => void,
|
|
||||||
onDirect: (account: any) => void,
|
|
||||||
onChat: (account: any, history: History) => void,
|
|
||||||
onMention: (account: any) => void,
|
|
||||||
onMute: (account: any) => void,
|
|
||||||
onBlock: (status: Status) => void,
|
|
||||||
onReport: (status: Status) => void,
|
|
||||||
onEmbed: (status: Status) => void,
|
|
||||||
onDeactivateUser: (status: Status) => void,
|
|
||||||
onDeleteUser: (status: Status) => void,
|
|
||||||
onToggleStatusSensitivity: (status: Status) => void,
|
|
||||||
onDeleteStatus: (status: Status) => void,
|
|
||||||
onMuteConversation: (status: Status) => void,
|
|
||||||
onPin: (status: Status) => void,
|
|
||||||
withDismiss?: boolean,
|
withDismiss?: boolean,
|
||||||
withGroupAdmin?: boolean,
|
|
||||||
allowedEmoji: ImmutableList<string>,
|
|
||||||
emojiSelectorFocused: boolean,
|
|
||||||
handleEmojiSelectorUnfocus: () => void,
|
|
||||||
handleEmojiSelectorExpand?: React.EventHandler<React.KeyboardEvent>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatusActionBar: React.FC<IStatusActionBar> = ({
|
const StatusActionBar: React.FC<IStatusActionBar> = ({ status, withDismiss = false }) => {
|
||||||
status,
|
|
||||||
onReply,
|
|
||||||
onFavourite,
|
|
||||||
allowedEmoji,
|
|
||||||
onBookmark,
|
|
||||||
onReblog,
|
|
||||||
onQuote,
|
|
||||||
onDelete,
|
|
||||||
onEdit,
|
|
||||||
onPin,
|
|
||||||
onMention,
|
|
||||||
onDirect,
|
|
||||||
onChat,
|
|
||||||
onMute,
|
|
||||||
onBlock,
|
|
||||||
onEmbed,
|
|
||||||
onReport,
|
|
||||||
onMuteConversation,
|
|
||||||
onDeactivateUser,
|
|
||||||
onDeleteUser,
|
|
||||||
onDeleteStatus,
|
|
||||||
onToggleStatusSensitivity,
|
|
||||||
withDismiss,
|
|
||||||
}) => {
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const me = useAppSelector(state => state.me);
|
const me = useAppSelector(state => state.me);
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
|
const settings = useSettings();
|
||||||
|
const soapboxConfig = useSoapboxConfig();
|
||||||
|
|
||||||
|
const { allowedEmoji } = soapboxConfig;
|
||||||
|
|
||||||
const account = useOwnAccount();
|
const account = useOwnAccount();
|
||||||
const isStaff = account ? account.staff : false;
|
const isStaff = account ? account.staff : false;
|
||||||
const isAdmin = account ? account.admin : false;
|
const isAdmin = account ? account.admin : false;
|
||||||
|
|
||||||
|
if (!status) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const onOpenUnauthorizedModal = (action?: string) => {
|
const onOpenUnauthorizedModal = (action?: string) => {
|
||||||
dispatch(openModal('UNAUTHORIZED', {
|
dispatch(openModal('UNAUTHORIZED', {
|
||||||
action,
|
action,
|
||||||
|
@ -137,7 +112,18 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
||||||
|
|
||||||
const handleReplyClick: React.MouseEventHandler = (e) => {
|
const handleReplyClick: React.MouseEventHandler = (e) => {
|
||||||
if (me) {
|
if (me) {
|
||||||
onReply(status);
|
dispatch((_, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
if (state.compose.text.trim().length !== 0) {
|
||||||
|
dispatch(openModal('CONFIRM', {
|
||||||
|
message: intl.formatMessage(messages.replyMessage),
|
||||||
|
confirm: intl.formatMessage(messages.replyConfirm),
|
||||||
|
onConfirm: () => dispatch(replyCompose(status)),
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
dispatch(replyCompose(status));
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
onOpenUnauthorizedModal('REPLY');
|
onOpenUnauthorizedModal('REPLY');
|
||||||
}
|
}
|
||||||
|
@ -156,7 +142,11 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
||||||
|
|
||||||
const handleFavouriteClick: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleFavouriteClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
if (me) {
|
if (me) {
|
||||||
onFavourite(status);
|
if (status.get('favourited')) {
|
||||||
|
dispatch(unfavourite(status));
|
||||||
|
} else {
|
||||||
|
dispatch(favourite(status));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
onOpenUnauthorizedModal('FAVOURITE');
|
onOpenUnauthorizedModal('FAVOURITE');
|
||||||
}
|
}
|
||||||
|
@ -166,14 +156,32 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
||||||
|
|
||||||
const handleBookmarkClick: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleBookmarkClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onBookmark(status);
|
|
||||||
|
if (status.get('bookmarked')) {
|
||||||
|
dispatch(unbookmark(status));
|
||||||
|
} else {
|
||||||
|
dispatch(bookmark(status));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const modalReblog = () => {
|
||||||
|
if (status.get('reblogged')) {
|
||||||
|
dispatch(unreblog(status));
|
||||||
|
} else {
|
||||||
|
dispatch(reblog(status));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReblogClick: React.EventHandler<React.MouseEvent> = e => {
|
const handleReblogClick: React.EventHandler<React.MouseEvent> = e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
if (me) {
|
if (me) {
|
||||||
onReblog(status, e);
|
const boostModal = settings.get('boostModal');
|
||||||
|
if ((e && e.shiftKey) || !boostModal) {
|
||||||
|
modalReblog();
|
||||||
|
} else {
|
||||||
|
dispatch(openModal('BOOST', { status, onReblog: modalReblog }));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
onOpenUnauthorizedModal('REBLOG');
|
onOpenUnauthorizedModal('REBLOG');
|
||||||
}
|
}
|
||||||
|
@ -183,54 +191,101 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
if (me) {
|
if (me) {
|
||||||
onQuote(status);
|
dispatch((_, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
if (state.compose.text.trim().length !== 0) {
|
||||||
|
dispatch(openModal('CONFIRM', {
|
||||||
|
message: intl.formatMessage(messages.replyMessage),
|
||||||
|
confirm: intl.formatMessage(messages.replyConfirm),
|
||||||
|
onConfirm: () => dispatch(quoteCompose(status)),
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
dispatch(quoteCompose(status));
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
onOpenUnauthorizedModal('REBLOG');
|
onOpenUnauthorizedModal('REBLOG');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const doDeleteStatus = (withRedraft = false) => {
|
||||||
|
dispatch((_, getState) => {
|
||||||
|
const deleteModal = settings.get('deleteModal');
|
||||||
|
if (!deleteModal) {
|
||||||
|
dispatch(deleteStatus(status.id, withRedraft));
|
||||||
|
} else {
|
||||||
|
dispatch(openModal('CONFIRM', {
|
||||||
|
icon: withRedraft ? require('@tabler/icons/edit.svg') : require('@tabler/icons/trash.svg'),
|
||||||
|
heading: intl.formatMessage(withRedraft ? messages.redraftHeading : messages.deleteHeading),
|
||||||
|
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
|
||||||
|
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
|
||||||
|
onConfirm: () => dispatch(deleteStatus(status.id, withRedraft)),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleDeleteClick: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleDeleteClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onDelete(status);
|
doDeleteStatus();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRedraftClick: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleRedraftClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onDelete(status, true);
|
doDeleteStatus(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditClick: React.EventHandler<React.MouseEvent> = () => {
|
const handleEditClick: React.EventHandler<React.MouseEvent> = () => {
|
||||||
onEdit(status);
|
dispatch(editStatus(status.id));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePinClick: React.EventHandler<React.MouseEvent> = (e) => {
|
const handlePinClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onPin(status);
|
|
||||||
|
if (status.get('pinned')) {
|
||||||
|
dispatch(unpin(status));
|
||||||
|
} else {
|
||||||
|
dispatch(pin(status));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMentionClick: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleMentionClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onMention(status.account);
|
dispatch(mentionCompose(status.account as Account));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDirectClick: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleDirectClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onDirect(status.account);
|
dispatch(directCompose(status.account as Account));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChatClick: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleChatClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onChat(status.account, history);
|
const account = status.account as Account;
|
||||||
|
dispatch(launchChat(account.id, history));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMuteClick: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleMuteClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onMute(status.account);
|
dispatch(initMuteModal(status.account as Account));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBlockClick: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleBlockClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onBlock(status);
|
|
||||||
|
const account = status.get('account') as Account;
|
||||||
|
dispatch(openModal('CONFIRM', {
|
||||||
|
icon: require('@tabler/icons/ban.svg'),
|
||||||
|
heading: <FormattedMessage id='confirmations.block.heading' defaultMessage='Block @{name}' values={{ name: account.get('acct') }} />,
|
||||||
|
message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
|
||||||
|
confirm: intl.formatMessage(messages.blockConfirm),
|
||||||
|
onConfirm: () => dispatch(blockAccount(account.id)),
|
||||||
|
secondary: intl.formatMessage(messages.blockAndReport),
|
||||||
|
onSecondary: () => {
|
||||||
|
dispatch(blockAccount(account.id));
|
||||||
|
dispatch(initReport(account, status));
|
||||||
|
},
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOpen: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleOpen: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
|
@ -239,17 +294,25 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEmbed = () => {
|
const handleEmbed = () => {
|
||||||
onEmbed(status);
|
dispatch(openModal('EMBED', {
|
||||||
|
url: status.get('url'),
|
||||||
|
onError: (error: any) => dispatch(showAlertForError(error)),
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReport: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleReport: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onReport(status);
|
dispatch(initReport(status.account as Account, status));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleConversationMuteClick: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleConversationMuteClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onMuteConversation(status);
|
|
||||||
|
if (status.get('muted')) {
|
||||||
|
dispatch(unmuteStatus(status.id));
|
||||||
|
} else {
|
||||||
|
dispatch(muteStatus(status.id));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopy: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleCopy: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
|
@ -275,22 +338,22 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
||||||
|
|
||||||
const handleDeactivateUser: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleDeactivateUser: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onDeactivateUser(status);
|
dispatch(deactivateUserModal(intl, status.getIn(['account', 'id']) as string));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteUser: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleDeleteUser: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onDeleteUser(status);
|
dispatch(deleteUserModal(intl, status.getIn(['account', 'id']) as string));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteStatus: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleDeleteStatus: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onDeleteStatus(status);
|
dispatch(deleteStatusModal(intl, status.id));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggleStatusSensitivity: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleToggleStatusSensitivity: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onToggleStatusSensitivity(status);
|
dispatch(toggleStatusSensitivityModal(intl, status.id, status.sensitive));
|
||||||
};
|
};
|
||||||
|
|
||||||
const _makeMenu = (publicStatus: boolean) => {
|
const _makeMenu = (publicStatus: boolean) => {
|
||||||
|
|
|
@ -3,46 +3,25 @@ import { List as ImmutableList, OrderedSet as ImmutableOrderedSet } from 'immuta
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { HotKeys } from 'react-hotkeys';
|
import { HotKeys } from 'react-hotkeys';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
import { blockAccount } from 'soapbox/actions/accounts';
|
|
||||||
import { launchChat } from 'soapbox/actions/chats';
|
|
||||||
import {
|
import {
|
||||||
replyCompose,
|
replyCompose,
|
||||||
mentionCompose,
|
mentionCompose,
|
||||||
directCompose,
|
|
||||||
quoteCompose,
|
|
||||||
} from 'soapbox/actions/compose';
|
} from 'soapbox/actions/compose';
|
||||||
import { simpleEmojiReact } from 'soapbox/actions/emoji_reacts';
|
|
||||||
import {
|
import {
|
||||||
favourite,
|
favourite,
|
||||||
unfavourite,
|
unfavourite,
|
||||||
reblog,
|
reblog,
|
||||||
unreblog,
|
unreblog,
|
||||||
bookmark,
|
|
||||||
unbookmark,
|
|
||||||
pin,
|
|
||||||
unpin,
|
|
||||||
} from 'soapbox/actions/interactions';
|
} from 'soapbox/actions/interactions';
|
||||||
import { openModal } from 'soapbox/actions/modals';
|
import { openModal } from 'soapbox/actions/modals';
|
||||||
import {
|
|
||||||
deactivateUserModal,
|
|
||||||
deleteUserModal,
|
|
||||||
deleteStatusModal,
|
|
||||||
toggleStatusSensitivityModal,
|
|
||||||
} from 'soapbox/actions/moderation';
|
|
||||||
import { initMuteModal } from 'soapbox/actions/mutes';
|
|
||||||
import { initReport } from 'soapbox/actions/reports';
|
|
||||||
import { getSettings } from 'soapbox/actions/settings';
|
import { getSettings } from 'soapbox/actions/settings';
|
||||||
import {
|
import {
|
||||||
muteStatus,
|
|
||||||
unmuteStatus,
|
|
||||||
deleteStatus,
|
|
||||||
hideStatus,
|
hideStatus,
|
||||||
revealStatus,
|
revealStatus,
|
||||||
editStatus,
|
|
||||||
fetchStatusWithContext,
|
fetchStatusWithContext,
|
||||||
fetchNext,
|
fetchNext,
|
||||||
} from 'soapbox/actions/statuses';
|
} from 'soapbox/actions/statuses';
|
||||||
|
@ -55,7 +34,7 @@ import Tombstone from 'soapbox/components/tombstone';
|
||||||
import { Column, Stack } from 'soapbox/components/ui';
|
import { Column, Stack } from 'soapbox/components/ui';
|
||||||
import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder_status';
|
import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder_status';
|
||||||
import PendingStatus from 'soapbox/features/ui/components/pending_status';
|
import PendingStatus from 'soapbox/features/ui/components/pending_status';
|
||||||
import { useAppDispatch, useAppSelector, useSettings, useSoapboxConfig } from 'soapbox/hooks';
|
import { useAppDispatch, useAppSelector, useSettings } from 'soapbox/hooks';
|
||||||
import { makeGetStatus } from 'soapbox/selectors';
|
import { makeGetStatus } from 'soapbox/selectors';
|
||||||
import { defaultMediaVisibility, textForScreenReader } from 'soapbox/utils/status';
|
import { defaultMediaVisibility, textForScreenReader } from 'soapbox/utils/status';
|
||||||
|
|
||||||
|
@ -63,7 +42,6 @@ import DetailedStatus from './components/detailed-status';
|
||||||
import ThreadLoginCta from './components/thread-login-cta';
|
import ThreadLoginCta from './components/thread-login-cta';
|
||||||
import ThreadStatus from './components/thread-status';
|
import ThreadStatus from './components/thread-status';
|
||||||
|
|
||||||
import type { History } from 'history';
|
|
||||||
import type { VirtuosoHandle } from 'react-virtuoso';
|
import type { VirtuosoHandle } from 'react-virtuoso';
|
||||||
import type { RootState } from 'soapbox/store';
|
import type { RootState } from 'soapbox/store';
|
||||||
import type {
|
import type {
|
||||||
|
@ -153,12 +131,10 @@ const Thread: React.FC<IThread> = (props) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const soapboxConfig = useSoapboxConfig();
|
|
||||||
|
|
||||||
const me = useAppSelector(state => state.me);
|
const me = useAppSelector(state => state.me);
|
||||||
const status = useAppSelector(state => getStatus(state, { id: props.params.statusId }));
|
const status = useAppSelector(state => getStatus(state, { id: props.params.statusId }));
|
||||||
const displayMedia = settings.get('displayMedia') as DisplayMedia;
|
const displayMedia = settings.get('displayMedia') as DisplayMedia;
|
||||||
const allowedEmoji = soapboxConfig.allowedEmoji;
|
|
||||||
const askReplyConfirmation = useAppSelector(state => state.compose.text.trim().length !== 0);
|
const askReplyConfirmation = useAppSelector(state => state.compose.text.trim().length !== 0);
|
||||||
|
|
||||||
const { ancestorsIds, descendantsIds } = useAppSelector(state => {
|
const { ancestorsIds, descendantsIds } = useAppSelector(state => {
|
||||||
|
@ -181,7 +157,6 @@ const Thread: React.FC<IThread> = (props) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const [showMedia, setShowMedia] = useState<boolean>(defaultMediaVisibility(status, displayMedia));
|
const [showMedia, setShowMedia] = useState<boolean>(defaultMediaVisibility(status, displayMedia));
|
||||||
const [emojiSelectorFocused, setEmojiSelectorFocused] = useState(false);
|
|
||||||
const [isLoaded, setIsLoaded] = useState<boolean>(!!status);
|
const [isLoaded, setIsLoaded] = useState<boolean>(!!status);
|
||||||
const [next, setNext] = useState<string>();
|
const [next, setNext] = useState<string>();
|
||||||
|
|
||||||
|
@ -210,8 +185,11 @@ const Thread: React.FC<IThread> = (props) => {
|
||||||
setShowMedia(!showMedia);
|
setShowMedia(!showMedia);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEmojiReactClick = (status: StatusEntity, emoji: string) => {
|
const handleHotkeyReact = () => {
|
||||||
dispatch(simpleEmojiReact(status, emoji));
|
if (statusRef.current) {
|
||||||
|
const firstEmoji: HTMLButtonElement | null = statusRef.current.querySelector('.emoji-react-selector .emoji-react-selector__emoji');
|
||||||
|
firstEmoji?.focus();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFavouriteClick = (status: StatusEntity) => {
|
const handleFavouriteClick = (status: StatusEntity) => {
|
||||||
|
@ -222,22 +200,6 @@ const Thread: React.FC<IThread> = (props) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePin = (status: StatusEntity) => {
|
|
||||||
if (status.pinned) {
|
|
||||||
dispatch(unpin(status));
|
|
||||||
} else {
|
|
||||||
dispatch(pin(status));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBookmark = (status: StatusEntity) => {
|
|
||||||
if (status.bookmarked) {
|
|
||||||
dispatch(unbookmark(status));
|
|
||||||
} else {
|
|
||||||
dispatch(bookmark(status));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleReplyClick = (status: StatusEntity) => {
|
const handleReplyClick = (status: StatusEntity) => {
|
||||||
if (askReplyConfirmation) {
|
if (askReplyConfirmation) {
|
||||||
dispatch(openModal('CONFIRM', {
|
dispatch(openModal('CONFIRM', {
|
||||||
|
@ -269,47 +231,6 @@ const Thread: React.FC<IThread> = (props) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleQuoteClick = (status: StatusEntity) => {
|
|
||||||
if (askReplyConfirmation) {
|
|
||||||
dispatch(openModal('CONFIRM', {
|
|
||||||
message: intl.formatMessage(messages.replyMessage),
|
|
||||||
confirm: intl.formatMessage(messages.replyConfirm),
|
|
||||||
onConfirm: () => dispatch(quoteCompose(status)),
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
dispatch(quoteCompose(status));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteClick = (status: StatusEntity, withRedraft = false) => {
|
|
||||||
dispatch((_, getState) => {
|
|
||||||
const deleteModal = getSettings(getState()).get('deleteModal');
|
|
||||||
if (!deleteModal) {
|
|
||||||
dispatch(deleteStatus(status.id, withRedraft));
|
|
||||||
} else {
|
|
||||||
dispatch(openModal('CONFIRM', {
|
|
||||||
icon: withRedraft ? require('@tabler/icons/edit.svg') : require('@tabler/icons/trash.svg'),
|
|
||||||
heading: intl.formatMessage(withRedraft ? messages.redraftHeading : messages.deleteHeading),
|
|
||||||
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
|
|
||||||
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
|
|
||||||
onConfirm: () => dispatch(deleteStatus(status.id, withRedraft)),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditClick = (status: StatusEntity) => {
|
|
||||||
dispatch(editStatus(status.id));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDirectClick = (account: AccountEntity) => {
|
|
||||||
dispatch(directCompose(account));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChatClick = (account: AccountEntity, router: History) => {
|
|
||||||
dispatch(launchChat(account.id, router));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMentionClick = (account: AccountEntity) => {
|
const handleMentionClick = (account: AccountEntity) => {
|
||||||
dispatch(mentionCompose(account));
|
dispatch(mentionCompose(account));
|
||||||
};
|
};
|
||||||
|
@ -337,18 +258,6 @@ const Thread: React.FC<IThread> = (props) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMuteClick = (account: AccountEntity) => {
|
|
||||||
dispatch(initMuteModal(account));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleConversationMuteClick = (status: StatusEntity) => {
|
|
||||||
if (status.muted) {
|
|
||||||
dispatch(unmuteStatus(status.id));
|
|
||||||
} else {
|
|
||||||
dispatch(muteStatus(status.id));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleToggleHidden = (status: StatusEntity) => {
|
const handleToggleHidden = (status: StatusEntity) => {
|
||||||
if (status.hidden) {
|
if (status.hidden) {
|
||||||
dispatch(revealStatus(status.id));
|
dispatch(revealStatus(status.id));
|
||||||
|
@ -357,48 +266,6 @@ const Thread: React.FC<IThread> = (props) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBlockClick = (status: StatusEntity) => {
|
|
||||||
const { account } = status;
|
|
||||||
if (!account || typeof account !== 'object') return;
|
|
||||||
|
|
||||||
dispatch(openModal('CONFIRM', {
|
|
||||||
icon: require('@tabler/icons/ban.svg'),
|
|
||||||
heading: <FormattedMessage id='confirmations.block.heading' defaultMessage='Block @{name}' values={{ name: account.acct }} />,
|
|
||||||
message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.acct}</strong> }} />,
|
|
||||||
confirm: intl.formatMessage(messages.blockConfirm),
|
|
||||||
onConfirm: () => dispatch(blockAccount(account.id)),
|
|
||||||
secondary: intl.formatMessage(messages.blockAndReport),
|
|
||||||
onSecondary: () => {
|
|
||||||
dispatch(blockAccount(account.id));
|
|
||||||
dispatch(initReport(account, status));
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleReport = (status: StatusEntity) => {
|
|
||||||
dispatch(initReport(status.account as AccountEntity, status));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEmbed = (status: StatusEntity) => {
|
|
||||||
dispatch(openModal('EMBED', { url: status.url }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeactivateUser = (status: StatusEntity) => {
|
|
||||||
dispatch(deactivateUserModal(intl, status.getIn(['account', 'id']) as string));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteUser = (status: StatusEntity) => {
|
|
||||||
dispatch(deleteUserModal(intl, status.getIn(['account', 'id']) as string));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleToggleStatusSensitivity = (status: StatusEntity) => {
|
|
||||||
dispatch(toggleStatusSensitivityModal(intl, status.id, status.sensitive));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteStatus = (status: StatusEntity) => {
|
|
||||||
dispatch(deleteStatusModal(intl, status.id));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleHotkeyMoveUp = () => {
|
const handleHotkeyMoveUp = () => {
|
||||||
handleMoveUp(status!.id);
|
handleMoveUp(status!.id);
|
||||||
};
|
};
|
||||||
|
@ -439,10 +306,6 @@ const Thread: React.FC<IThread> = (props) => {
|
||||||
handleToggleMediaVisibility();
|
handleToggleMediaVisibility();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleHotkeyReact = () => {
|
|
||||||
_expandEmojiSelector();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMoveUp = (id: string) => {
|
const handleMoveUp = (id: string) => {
|
||||||
if (id === status?.id) {
|
if (id === status?.id) {
|
||||||
_selectChild(ancestorsIds.size - 1);
|
_selectChild(ancestorsIds.size - 1);
|
||||||
|
@ -473,25 +336,6 @@ const Thread: React.FC<IThread> = (props) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEmojiSelectorExpand: React.EventHandler<React.KeyboardEvent> = e => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
_expandEmojiSelector();
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEmojiSelectorUnfocus = () => {
|
|
||||||
setEmojiSelectorFocused(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const _expandEmojiSelector = () => {
|
|
||||||
if (statusRef.current) {
|
|
||||||
setEmojiSelectorFocused(true);
|
|
||||||
const firstEmoji: HTMLButtonElement | null = statusRef.current.querySelector('.emoji-react-selector .emoji-react-selector__emoji');
|
|
||||||
firstEmoji?.focus();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const _selectChild = (index: number) => {
|
const _selectChild = (index: number) => {
|
||||||
scroller.current?.scrollIntoView({
|
scroller.current?.scrollIntoView({
|
||||||
index,
|
index,
|
||||||
|
@ -640,34 +484,7 @@ const Thread: React.FC<IThread> = (props) => {
|
||||||
|
|
||||||
<hr className='mb-2 border-t-2 dark:border-primary-800' />
|
<hr className='mb-2 border-t-2 dark:border-primary-800' />
|
||||||
|
|
||||||
<StatusActionBar
|
<StatusActionBar status={status} />
|
||||||
status={status}
|
|
||||||
onReply={handleReplyClick}
|
|
||||||
onFavourite={handleFavouriteClick}
|
|
||||||
onEmojiReact={handleEmojiReactClick}
|
|
||||||
onReblog={handleReblogClick}
|
|
||||||
onQuote={handleQuoteClick}
|
|
||||||
onDelete={handleDeleteClick}
|
|
||||||
onEdit={handleEditClick}
|
|
||||||
onDirect={handleDirectClick}
|
|
||||||
onChat={handleChatClick}
|
|
||||||
onMention={handleMentionClick}
|
|
||||||
onMute={handleMuteClick}
|
|
||||||
onMuteConversation={handleConversationMuteClick}
|
|
||||||
onBlock={handleBlockClick}
|
|
||||||
onReport={handleReport}
|
|
||||||
onPin={handlePin}
|
|
||||||
onBookmark={handleBookmark}
|
|
||||||
onEmbed={handleEmbed}
|
|
||||||
onDeactivateUser={handleDeactivateUser}
|
|
||||||
onDeleteUser={handleDeleteUser}
|
|
||||||
onToggleStatusSensitivity={handleToggleStatusSensitivity}
|
|
||||||
onDeleteStatus={handleDeleteStatus}
|
|
||||||
allowedEmoji={allowedEmoji}
|
|
||||||
emojiSelectorFocused={emojiSelectorFocused}
|
|
||||||
handleEmojiSelectorExpand={handleEmojiSelectorExpand}
|
|
||||||
handleEmojiSelectorUnfocus={handleEmojiSelectorUnfocus}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</HotKeys>
|
</HotKeys>
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue