From 5f83ba9324e682c6875daf9bcdc04a28ea046727 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Wed, 9 Nov 2022 13:43:15 -0500 Subject: [PATCH 1/5] 'inReview' -> 'isUnderReview' --- app/soapbox/components/status.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/soapbox/components/status.tsx b/app/soapbox/components/status.tsx index 961ca5fbe..2e05c7a84 100644 --- a/app/soapbox/components/status.tsx +++ b/app/soapbox/components/status.tsx @@ -296,7 +296,7 @@ const Status: React.FC = (props) => { const accountAction = props.accountAction || reblogElement; - const inReview = actualStatus.visibility === 'self'; + const isUnderReview = actualStatus.visibility === 'self'; const isSensitive = actualStatus.hidden; return ( @@ -354,11 +354,11 @@ const Status: React.FC = (props) => { - {(inReview || isSensitive) && ( + {(isUnderReview || isSensitive) && ( Date: Wed, 9 Nov 2022 14:03:38 -0500 Subject: [PATCH 2/5] Hide action bar if status is under review --- app/soapbox/components/status.tsx | 2 +- app/soapbox/features/status/index.tsx | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/app/soapbox/components/status.tsx b/app/soapbox/components/status.tsx index 2e05c7a84..f7bc351b2 100644 --- a/app/soapbox/components/status.tsx +++ b/app/soapbox/components/status.tsx @@ -392,7 +392,7 @@ const Status: React.FC = (props) => { - {!hideActionBar && ( + {(!hideActionBar && !isUnderReview) && (
diff --git a/app/soapbox/features/status/index.tsx b/app/soapbox/features/status/index.tsx index dbcc373ee..2d419ca84 100644 --- a/app/soapbox/features/status/index.tsx +++ b/app/soapbox/features/status/index.tsx @@ -134,6 +134,7 @@ const Thread: React.FC = (props) => { const me = useAppSelector(state => state.me); const status = useAppSelector(state => getStatus(state, { id: props.params.statusId })); const displayMedia = settings.get('displayMedia') as DisplayMedia; + const isUnderReview = status?.visibility === 'self'; const { ancestorsIds, descendantsIds } = useAppSelector(state => { let ancestorsIds = ImmutableOrderedSet(); @@ -412,7 +413,7 @@ const Thread: React.FC = (props) => { if (next && status) { dispatch(fetchNext(status.id, next)).then(({ next }) => { setNext(next); - }).catch(() => {}); + }).catch(() => { }); } }, 300, { leading: true }), [next, status]); @@ -475,14 +476,18 @@ const Thread: React.FC = (props) => { onOpenCompareHistoryModal={handleOpenCompareHistoryModal} /> -
+ {!isUnderReview ? ( + <> +
- + + + ) : null} From ce6915cded7c793043525af1d5c406e76d676c10 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Wed, 9 Nov 2022 14:23:33 -0500 Subject: [PATCH 3/5] Add test for StatusActionBar --- .../components/__tests__/status.test.tsx | 42 +++++++++++++++++++ app/soapbox/components/status-action-bar.tsx | 5 ++- 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 app/soapbox/components/__tests__/status.test.tsx diff --git a/app/soapbox/components/__tests__/status.test.tsx b/app/soapbox/components/__tests__/status.test.tsx new file mode 100644 index 000000000..ea9d04d98 --- /dev/null +++ b/app/soapbox/components/__tests__/status.test.tsx @@ -0,0 +1,42 @@ +import React from 'react'; + +import { render, screen, rootState } from '../../jest/test-helpers'; +import { normalizeStatus, normalizeAccount } from '../../normalizers'; +import Status from '../status'; + +import type { ReducerStatus } from 'soapbox/reducers/statuses'; + +const account = normalizeAccount({ + id: '1', + acct: 'alex', +}); + +const status = normalizeStatus({ + id: '1', + account, + content: 'hello world', + contentHtml: 'hello world', +}) as ReducerStatus; + +describe('', () => { + const state = rootState.setIn(['accounts', '1'], account); + + it('renders content', () => { + render(, undefined, state); + screen.getByText(/hello world/i); + expect(screen.getByTestId('status')).toHaveTextContent(/hello world/i); + }); + + describe('the Status Action Bar', () => { + it('is rendered', () => { + render(, undefined, state); + expect(screen.getByTestId('status-action-bar')).toBeInTheDocument(); + }); + + it('is not rendered if status is under review', () => { + const inReviewStatus = normalizeStatus({ ...status, visibility: 'self' }); + render(, undefined, state); + expect(screen.queryAllByTestId('status-action-bar')).toHaveLength(0); + }); + }); +}); diff --git a/app/soapbox/components/status-action-bar.tsx b/app/soapbox/components/status-action-bar.tsx index a5bd99d71..574d5f6e0 100644 --- a/app/soapbox/components/status-action-bar.tsx +++ b/app/soapbox/components/status-action-bar.tsx @@ -279,12 +279,12 @@ const StatusActionBar: React.FC = ({ }; const handleCopy: React.EventHandler = (e) => { - const { uri } = status; + const { uri } = status; const textarea = document.createElement('textarea'); e.stopPropagation(); - textarea.textContent = uri; + textarea.textContent = uri; textarea.style.position = 'fixed'; document.body.appendChild(textarea); @@ -552,6 +552,7 @@ const StatusActionBar: React.FC = ({ return (
Date: Thu, 10 Nov 2022 17:01:41 +0100 Subject: [PATCH 4/5] Improve click handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/components/account.tsx | 10 +++++++-- app/soapbox/components/dropdown_menu.tsx | 11 ++++++---- app/soapbox/components/quoted-status.tsx | 7 +++++- app/soapbox/components/status-action-bar.tsx | 1 - .../components/status-reply-mentions.tsx | 2 +- app/soapbox/components/status.tsx | 22 ++++++++++++------- app/soapbox/components/status_content.tsx | 2 +- app/soapbox/components/translate-button.tsx | 2 +- .../features/ui/components/actions_modal.tsx | 2 +- app/styles/components/modal.scss | 2 +- 10 files changed, 40 insertions(+), 21 deletions(-) diff --git a/app/soapbox/components/account.tsx b/app/soapbox/components/account.tsx index 9f2d17a0c..e981e66ae 100644 --- a/app/soapbox/components/account.tsx +++ b/app/soapbox/components/account.tsx @@ -22,7 +22,13 @@ const InstanceFavicon: React.FC = ({ account }) => { const handleClick: React.MouseEventHandler = (e) => { e.stopPropagation(); - history.push(`/timeline/${account.domain}`); + + const timelineUrl = `/timeline/${account.domain}`; + if (!(e.ctrlKey || e.metaKey)) { + history.push(timelineUrl); + } else { + window.open(timelineUrl, '_blank'); + } }; return ( @@ -219,7 +225,7 @@ const Account = ({ · {timestampUrl ? ( - + event.stopPropagation()}> ) : ( diff --git a/app/soapbox/components/dropdown_menu.tsx b/app/soapbox/components/dropdown_menu.tsx index bb20c8622..01d69483d 100644 --- a/app/soapbox/components/dropdown_menu.tsx +++ b/app/soapbox/components/dropdown_menu.tsx @@ -143,12 +143,14 @@ class DropdownMenu extends React.PureComponent {icon && } diff --git a/app/soapbox/components/quoted-status.tsx b/app/soapbox/components/quoted-status.tsx index b521c1172..863a5d3af 100644 --- a/app/soapbox/components/quoted-status.tsx +++ b/app/soapbox/components/quoted-status.tsx @@ -44,7 +44,12 @@ const QuotedStatus: React.FC = ({ status, onCancel, compose }) => const account = status.account as AccountEntity; if (!compose && e.button === 0) { - history.push(`/@${account.acct}/posts/${status.id}`); + const statusUrl = `/@${account.acct}/posts/${status.id}`; + if (!(e.ctrlKey || e.metaKey)) { + history.push(statusUrl); + } else { + window.open(statusUrl, '_blank'); + } e.stopPropagation(); e.preventDefault(); } diff --git a/app/soapbox/components/status-action-bar.tsx b/app/soapbox/components/status-action-bar.tsx index a5bd99d71..df737d7fc 100644 --- a/app/soapbox/components/status-action-bar.tsx +++ b/app/soapbox/components/status-action-bar.tsx @@ -459,7 +459,6 @@ const StatusActionBar: React.FC = ({ text: intl.formatMessage(messages.admin_status), href: `/pleroma/admin/#/statuses/${status.id}/`, icon: require('@tabler/icons/pencil.svg'), - action: (event) => event.stopPropagation(), }); } diff --git a/app/soapbox/components/status-reply-mentions.tsx b/app/soapbox/components/status-reply-mentions.tsx index ce0451fda..4d65abd11 100644 --- a/app/soapbox/components/status-reply-mentions.tsx +++ b/app/soapbox/components/status-reply-mentions.tsx @@ -50,7 +50,7 @@ const StatusReplyMentions: React.FC = ({ status, hoverable // The typical case with a reply-to and a list of mentions. const accounts = to.slice(0, 2).map(account => { const link = ( - @{account.username} + e.stopPropagation()}>@{account.username} ); if (hoverable) { diff --git a/app/soapbox/components/status.tsx b/app/soapbox/components/status.tsx index 961ca5fbe..1a2179df8 100644 --- a/app/soapbox/components/status.tsx +++ b/app/soapbox/components/status.tsx @@ -84,6 +84,8 @@ const Status: React.FC = (props) => { const actualStatus = getActualStatus(status); + const statusUrl = `/@${actualStatus.getIn(['account', 'acct'])}/posts/${actualStatus.id}`; + // Track height changes we know about to compensate scrolling. useEffect(() => { didShowCard.current = Boolean(!muted && !hidden && status?.card); @@ -97,11 +99,17 @@ const Status: React.FC = (props) => { setShowMedia(!showMedia); }; - const handleClick = (): void => { - if (onClick) { - onClick(); + const handleClick = (e?: React.MouseEvent): void => { + e?.stopPropagation(); + + if (!e || !(e.ctrlKey || e.metaKey)) { + if (onClick) { + onClick(); + } else { + history.push(statusUrl); + } } else { - history.push(`/@${actualStatus.getIn(['account', 'acct'])}/posts/${actualStatus.id}`); + window.open(statusUrl, '_blank'); } }; @@ -145,7 +153,7 @@ const Status: React.FC = (props) => { }; const handleHotkeyOpen = (): void => { - history.push(`/@${actualStatus.getIn(['account', 'acct'])}/posts/${actualStatus.id}`); + history.push(statusUrl); }; const handleHotkeyOpenProfile = (): void => { @@ -292,8 +300,6 @@ const Status: React.FC = (props) => { react: handleHotkeyReact, }; - const statusUrl = `/@${actualStatus.getIn(['account', 'acct'])}/posts/${actualStatus.id}`; - const accountAction = props.accountAction || reblogElement; const inReview = actualStatus.visibility === 'self'; @@ -307,7 +313,7 @@ const Status: React.FC = (props) => { data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, actualStatus, rebloggedByText)} ref={node} - onClick={() => history.push(statusUrl)} + onClick={handleClick} role='link' > {featured && ( diff --git a/app/soapbox/components/status_content.tsx b/app/soapbox/components/status_content.tsx index 70ee87a15..b3592c62d 100644 --- a/app/soapbox/components/status_content.tsx +++ b/app/soapbox/components/status_content.tsx @@ -147,7 +147,7 @@ const StatusContent: React.FC = ({ status, onClick, collapsable return; } - if (deltaX + deltaY < 5 && e.button === 0 && onClick) { + if (deltaX + deltaY < 5 && e.button === 0 && !(e.ctrlKey || e.metaKey) && onClick) { onClick(); } diff --git a/app/soapbox/components/translate-button.tsx b/app/soapbox/components/translate-button.tsx index d39cba1a6..07c778fd2 100644 --- a/app/soapbox/components/translate-button.tsx +++ b/app/soapbox/components/translate-button.tsx @@ -19,7 +19,7 @@ const TranslateButton: React.FC = ({ status }) => { const me = useAppSelector((state) => state.me); - const renderTranslate = /* translationEnabled && */ me && ['public', 'unlisted'].includes(status.visibility) && status.contentHtml.length > 0 && status.language !== null && intl.locale !== status.language; + const renderTranslate = me && ['public', 'unlisted'].includes(status.visibility) && status.contentHtml.length > 0 && status.language !== null && intl.locale !== status.language; const handleTranslate: React.MouseEventHandler = (e) => { e.stopPropagation(); diff --git a/app/soapbox/features/ui/components/actions_modal.tsx b/app/soapbox/features/ui/components/actions_modal.tsx index 27faaf909..cc00126f6 100644 --- a/app/soapbox/features/ui/components/actions_modal.tsx +++ b/app/soapbox/features/ui/components/actions_modal.tsx @@ -40,7 +40,7 @@ const ActionsModal: React.FC = ({ status, actions, onClick, onClo className={classNames('w-full', { active, destructive })} data-method={isLogout ? 'delete' : null} > - {icon && } + {icon && }
{text}
{meta}
diff --git a/app/styles/components/modal.scss b/app/styles/components/modal.scss index f663c26ff..0ac611f91 100644 --- a/app/styles/components/modal.scss +++ b/app/styles/components/modal.scss @@ -311,7 +311,7 @@ } .svg-icon:first-child { - @apply w-5 h-5 mr-2.5; + @apply min-w-[1.25rem] w-5 h-5 mr-2.5; svg { stroke-width: 1.5; From 4d9e4879ed5e853fbbb737b00769d2c10e6d7ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 10 Nov 2022 21:46:51 +0100 Subject: [PATCH 5/5] Fix Escape key not working in modals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/components/modal_root.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/soapbox/components/modal_root.tsx b/app/soapbox/components/modal_root.tsx index eabd450d0..f34597d5b 100644 --- a/app/soapbox/components/modal_root.tsx +++ b/app/soapbox/components/modal_root.tsx @@ -47,12 +47,13 @@ const ModalRoot: React.FC = ({ children, onCancel, onClose, type }) const isEditing = useAppSelector(state => state.compose.get('compose-modal')?.id !== null); - const handleKeyUp = useCallback((e) => { - if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27) - && !!children) { + const visible = !!children; + + const handleKeyUp = (e: KeyboardEvent) => { + if (e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27) { handleOnClose(); } - }, []); + }; const handleOnClose = () => { dispatch((_, getState) => { @@ -136,6 +137,8 @@ const ModalRoot: React.FC = ({ children, onCancel, onClose, type }) }; useEffect(() => { + if (!visible) return; + window.addEventListener('keyup', handleKeyUp, false); window.addEventListener('keydown', handleKeyDown, false); @@ -143,7 +146,7 @@ const ModalRoot: React.FC = ({ children, onCancel, onClose, type }) window.removeEventListener('keyup', handleKeyUp); window.removeEventListener('keydown', handleKeyDown); }; - }, []); + }, [visible]); useEffect(() => { if (!!children && !prevChildren) { @@ -172,8 +175,6 @@ const ModalRoot: React.FC = ({ children, onCancel, onClose, type }) } }); - const visible = !!children; - if (!visible) { return (