From 7e32f0d992e90a856de755cbb1bae41682193222 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 19 Nov 2022 18:13:27 -0600 Subject: [PATCH 1/8] Add component to style markup from the API --- .../{status-content.css => markup.css} | 40 +++++++-------- app/soapbox/components/markup.tsx | 16 ++++++ app/soapbox/components/status-content.tsx | 51 +++---------------- app/soapbox/components/ui/text/text.tsx | 8 +-- .../ui/components/profile-fields-panel.tsx | 7 +-- .../ui/components/profile-info-panel.tsx | 12 ++--- app/soapbox/normalizers/account.ts | 10 ++++ 7 files changed, 66 insertions(+), 78 deletions(-) rename app/soapbox/components/{status-content.css => markup.css} (62%) create mode 100644 app/soapbox/components/markup.tsx diff --git a/app/soapbox/components/status-content.css b/app/soapbox/components/markup.css similarity index 62% rename from app/soapbox/components/status-content.css rename to app/soapbox/components/markup.css index 997df73f4..d89848f2c 100644 --- a/app/soapbox/components/status-content.css +++ b/app/soapbox/components/markup.css @@ -1,77 +1,77 @@ -.status-content p { +[data-markup] p { @apply mb-4 whitespace-pre-wrap; } -.status-content p:last-child { +[data-markup] p:last-child { @apply mb-0; } -.status-content a { +[data-markup] a { @apply text-primary-600 dark:text-accent-blue hover:underline; } -.status-content strong { +[data-markup] strong { @apply font-bold; } -.status-content em { +[data-markup] em { @apply italic; } -.status-content ul, -.status-content ol { +[data-markup] ul, +[data-markup] ol { @apply pl-10 mb-4; } -.status-content ul { +[data-markup] ul { @apply list-disc list-outside; } -.status-content ol { +[data-markup] ol { @apply list-decimal list-outside; } -.status-content blockquote { +[data-markup] blockquote { @apply py-1 pl-4 mb-4 border-l-4 border-solid border-gray-400 text-gray-500 dark:text-gray-400; } -.status-content code { +[data-markup] code { @apply cursor-text font-mono; } -.status-content p > code, -.status-content pre { +[data-markup] p > code, +[data-markup] pre { @apply bg-gray-100 dark:bg-primary-800; } /* Inline code */ -.status-content p > code { +[data-markup] p > code { @apply py-0.5 px-1 rounded-sm; } /* Code block */ -.status-content pre { +[data-markup] pre { @apply py-2 px-3 mb-4 leading-6 overflow-x-auto rounded-md break-all; } -.status-content pre:last-child { +[data-markup] pre:last-child { @apply mb-0; } /* Markdown images */ -.status-content img:not(.emojione):not([width][height]) { +[data-markup] img:not(.emojione):not([width][height]) { @apply w-full h-72 object-contain rounded-lg overflow-hidden my-4 block; } /* User setting to underline links */ -body.underline-links .status-content a { +body.underline-links [data-markup] a { @apply underline; } -.status-content .big-emoji img.emojione { +[data-markup] .big-emoji img.emojione { @apply inline w-9 h-9 p-1; } -.status-content .status-link { +[data-markup] .status-link { @apply hover:underline text-primary-600 dark:text-accent-blue hover:text-primary-800 dark:hover:text-accent-blue; } diff --git a/app/soapbox/components/markup.tsx b/app/soapbox/components/markup.tsx new file mode 100644 index 000000000..e20dcb3a2 --- /dev/null +++ b/app/soapbox/components/markup.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import Text, { IText } from './ui/text/text'; +import './markup.css'; + +interface IMarkup extends IText { +} + +/** Styles HTML markup returned by the API, such as in account bios and statuses. */ +const Markup = React.forwardRef((props, ref) => { + return ( + + ); +}); + +export default Markup; \ No newline at end of file diff --git a/app/soapbox/components/status-content.tsx b/app/soapbox/components/status-content.tsx index 2b2754176..6bfbb8e18 100644 --- a/app/soapbox/components/status-content.tsx +++ b/app/soapbox/components/status-content.tsx @@ -10,8 +10,8 @@ import { onlyEmoji as isOnlyEmoji } from 'soapbox/utils/rich-content'; import { isRtl } from '../rtl'; +import Markup from './markup'; import Poll from './polls/poll'; -import './status-content.css'; import StopPropagation from './stop-propagation'; import type { Status, Mention } from 'soapbox/types/entities'; @@ -19,11 +19,6 @@ import type { Status, Mention } from 'soapbox/types/entities'; const MAX_HEIGHT = 642; // 20px * 32 (+ 2px padding at the top) const BIG_EMOJI_LIMIT = 10; -type Point = [ - x: number, - y: number, -] - interface IReadMoreButton { onClick: React.MouseEventHandler, } @@ -52,7 +47,6 @@ const StatusContent: React.FC = ({ status, onClick, collapsable const [collapsed, setCollapsed] = useState(false); const [onlyEmoji, setOnlyEmoji] = useState(false); - const startXY = useRef(); const node = useRef(null); const { greentext } = useSoapboxConfig(); @@ -138,29 +132,6 @@ const StatusContent: React.FC = ({ status, onClick, collapsable updateStatusLinks(); }); - const handleMouseDown: React.EventHandler = (e) => { - startXY.current = [e.clientX, e.clientY]; - }; - - const handleMouseUp: React.EventHandler = (e) => { - if (!startXY.current) return; - const target = e.target as HTMLElement; - const parentNode = target.parentNode as HTMLElement; - - const [startX, startY] = startXY.current; - const [deltaX, deltaY] = [Math.abs(e.clientX - startX), Math.abs(e.clientY - startY)]; - - if (target.localName === 'button' || target.localName === 'a' || (parentNode && (parentNode.localName === 'button' || parentNode.localName === 'a'))) { - return; - } - - if (deltaX + deltaY < 5 && e.button === 0 && !(e.ctrlKey || e.metaKey) && onClick) { - onClick(); - } - - startXY.current = undefined; - }; - const parsedHtml = useMemo((): string => { const html = translatable && status.translation ? status.translation.get('content')! : status.contentHtml; @@ -180,30 +151,24 @@ const StatusContent: React.FC = ({ status, onClick, collapsable const baseClassName = 'text-gray-900 dark:text-gray-100 break-words text-ellipsis overflow-hidden relative focus:outline-none'; const content = { __html: parsedHtml }; - const directionStyle: React.CSSProperties = { direction: 'ltr' }; - const className = classNames(baseClassName, 'status-content', { + const direction = isRtl(status.search_index) ? 'rtl' : 'ltr'; + const className = classNames(baseClassName, { 'cursor-pointer': onClick, 'whitespace-normal': withSpoiler, 'max-h-[300px]': collapsed, 'leading-normal big-emoji': onlyEmoji, }); - if (isRtl(status.search_index)) { - directionStyle.direction = 'rtl'; - } - if (onClick) { const output = [ -
, ]; @@ -219,14 +184,14 @@ const StatusContent: React.FC = ({ status, onClick, collapsable return
{output}
; } else { const output = [ -
, diff --git a/app/soapbox/components/ui/text/text.tsx b/app/soapbox/components/ui/text/text.tsx index 7669f3d2a..221c070fc 100644 --- a/app/soapbox/components/ui/text/text.tsx +++ b/app/soapbox/components/ui/text/text.tsx @@ -54,7 +54,9 @@ export type Sizes = keyof typeof sizes type Tags = 'abbr' | 'p' | 'span' | 'pre' | 'time' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label' type Directions = 'ltr' | 'rtl' -interface IText extends Pick, 'dangerouslySetInnerHTML'> { +interface IText extends Pick, 'dangerouslySetInnerHTML' | 'tabIndex' | 'lang'> { + /** Text content. */ + children?: React.ReactNode, /** How to align the text. */ align?: keyof typeof alignments, /** Extra class names for the outer element. */ @@ -84,8 +86,8 @@ interface IText extends Pick, 'danger } /** UI-friendly text container with dark mode support. */ -const Text: React.FC = React.forwardRef( - (props: IText, ref: React.LegacyRef) => { +const Text = React.forwardRef( + (props, ref) => { const { align, className, diff --git a/app/soapbox/features/ui/components/profile-fields-panel.tsx b/app/soapbox/features/ui/components/profile-fields-panel.tsx index dfa4dc84a..1ee911e34 100644 --- a/app/soapbox/features/ui/components/profile-fields-panel.tsx +++ b/app/soapbox/features/ui/components/profile-fields-panel.tsx @@ -2,7 +2,8 @@ import classNames from 'clsx'; import React from 'react'; import { defineMessages, useIntl, FormattedMessage, FormatDateOptions } from 'react-intl'; -import { Widget, Stack, HStack, Icon, Text } from 'soapbox/components/ui'; +import Markup from 'soapbox/components/markup'; +import { Widget, Stack, HStack, Icon } from 'soapbox/components/ui'; import BundleContainer from 'soapbox/features/ui/containers/bundle-container'; import { CryptoAddress } from 'soapbox/features/ui/util/async-components'; @@ -51,7 +52,7 @@ const ProfileField: React.FC = ({ field }) => { return (
- +
= ({ field }) => { )} - +
diff --git a/app/soapbox/features/ui/components/profile-info-panel.tsx b/app/soapbox/features/ui/components/profile-info-panel.tsx index cf97b78e7..89239bd61 100644 --- a/app/soapbox/features/ui/components/profile-info-panel.tsx +++ b/app/soapbox/features/ui/components/profile-info-panel.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; import Badge from 'soapbox/components/badge'; +import Markup from 'soapbox/components/markup'; import { Icon, HStack, Stack, Text } from 'soapbox/components/ui'; import VerificationBadge from 'soapbox/components/verification-badge'; import { useSoapboxConfig } from 'soapbox/hooks'; @@ -139,13 +140,6 @@ const ProfileInfoPanel: React.FC = ({ account, username }) => return (
- {/* Not sure if this is actual used. */} - {/*
- -
*/} - @@ -178,8 +172,8 @@ const ProfileInfoPanel: React.FC = ({ account, username }) => - {account.note.length > 0 && account.note !== '

' && ( - + {account.note.length > 0 && ( + )}
diff --git a/app/soapbox/normalizers/account.ts b/app/soapbox/normalizers/account.ts index 07d3ec1e6..39541facf 100644 --- a/app/soapbox/normalizers/account.ts +++ b/app/soapbox/normalizers/account.ts @@ -269,6 +269,15 @@ const fixBirthday = (account: ImmutableMap) => { return account.set('birthday', birthday || ''); }; +/** Rewrite `

` to empty string. */ +const fixNote = (account: ImmutableMap) => { + if (account.get('note') === '

') { + return account.set('note', ''); + } else { + return account; + } +}; + export const normalizeAccount = (account: Record) => { return AccountRecord( ImmutableMap(fromJS(account)).withMutations(account => { @@ -289,6 +298,7 @@ export const normalizeAccount = (account: Record) => { fixUsername(account); fixDisplayName(account); fixBirthday(account); + fixNote(account); addInternalFields(account); }), ); From 00989a7dfc169826eb73ea8c4c8b8264958674df Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 19 Nov 2022 18:28:34 -0600 Subject: [PATCH 2/8] LandingPage: use Markup component for instance description --- app/soapbox/features/landing-page/index.tsx | 13 +++++-------- .../features/landing-page/instance-description.css | 14 -------------- 2 files changed, 5 insertions(+), 22 deletions(-) delete mode 100644 app/soapbox/features/landing-page/instance-description.css diff --git a/app/soapbox/features/landing-page/index.tsx b/app/soapbox/features/landing-page/index.tsx index b5332fa34..08b9c2242 100644 --- a/app/soapbox/features/landing-page/index.tsx +++ b/app/soapbox/features/landing-page/index.tsx @@ -2,14 +2,13 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { prepareRequest } from 'soapbox/actions/consumer-auth'; +import Markup from 'soapbox/components/markup'; import { Button, Card, CardBody, Stack, Text } from 'soapbox/components/ui'; import VerificationBadge from 'soapbox/components/verification-badge'; import RegistrationForm from 'soapbox/features/auth-login/components/registration-form'; import { useAppDispatch, useAppSelector, useFeatures, useSoapboxConfig } from 'soapbox/hooks'; import { capitalize } from 'soapbox/utils/strings'; -import './instance-description.css'; - const LandingPage = () => { const dispatch = useAppDispatch(); const features = useFeatures(); @@ -114,12 +113,10 @@ const LandingPage = () => { {instance.title} - - - +
diff --git a/app/soapbox/features/landing-page/instance-description.css b/app/soapbox/features/landing-page/instance-description.css deleted file mode 100644 index f67097de0..000000000 --- a/app/soapbox/features/landing-page/instance-description.css +++ /dev/null @@ -1,14 +0,0 @@ -/* Instance HTML from the API. */ -.instance-description a { - @apply underline; -} - -.instance-description b, -.instance-description strong { - @apply font-bold; -} - -.instance-description i, -.instance-description em { - @apply italic; -} From 6444632324e990c68bdcf8c033789a18bbf4a225 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 20 Nov 2022 12:27:30 -0600 Subject: [PATCH 3/8] Revert "Merge branch 'autosuggest-profiles-fix' into 'develop'" This reverts commit 9df92e91e799618647ce8b3463a1512952a03a84, reversing changes made to 39b4ee9f09ebf9c7f438b27799be7a01944ba2a4. --- .../features/compose/components/autosuggest-account.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/app/soapbox/features/compose/components/autosuggest-account.tsx b/app/soapbox/features/compose/components/autosuggest-account.tsx index 54ab072c8..511d65fe6 100644 --- a/app/soapbox/features/compose/components/autosuggest-account.tsx +++ b/app/soapbox/features/compose/components/autosuggest-account.tsx @@ -14,14 +14,7 @@ const AutosuggestAccount: React.FC = ({ id }) => { if (!account) return null; - return ( -
- {/* HACK: The component stops click events, so insert this div as something to click. */} -
- - -
- ); + return ; }; From 8061c8d7825e9fe17cd7c602eff4fb44d873e6c7 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 19 Nov 2022 22:08:58 +0000 Subject: [PATCH 4/8] Revert "Merge branch 'mouseup' into 'develop'" This reverts commit 39b4ee9f09ebf9c7f438b27799be7a01944ba2a4, reversing changes made to a0597a644598983488334ca7c4243a34813b1d86. --- app/soapbox/components/account.tsx | 173 +++++++++--------- app/soapbox/components/polls/poll.tsx | 6 +- app/soapbox/components/quoted-status.tsx | 85 +++++---- app/soapbox/components/status-action-bar.tsx | 148 ++++++++------- app/soapbox/components/status-content.tsx | 15 +- app/soapbox/components/status-media.tsx | 11 +- .../components/status-reply-mentions.tsx | 59 +++--- app/soapbox/components/status.tsx | 6 +- .../statuses/sensitive-content-overlay.tsx | 77 ++++---- app/soapbox/components/stop-propagation.tsx | 33 ---- app/soapbox/components/translate-button.tsx | 17 +- app/soapbox/components/ui/button/button.tsx | 4 +- app/soapbox/components/ui/hstack/hstack.tsx | 2 +- app/soapbox/features/audio/index.tsx | 1 + .../ui/components/profile-dropdown.tsx | 7 +- app/soapbox/features/video/index.tsx | 3 + 16 files changed, 309 insertions(+), 338 deletions(-) delete mode 100644 app/soapbox/components/stop-propagation.tsx diff --git a/app/soapbox/components/account.tsx b/app/soapbox/components/account.tsx index db8d4b306..006584e8d 100644 --- a/app/soapbox/components/account.tsx +++ b/app/soapbox/components/account.tsx @@ -9,7 +9,6 @@ import { getAcct } from 'soapbox/utils/accounts'; import { displayFqn } from 'soapbox/utils/state'; import RelativeTimestamp from './relative-timestamp'; -import StopPropagation from './stop-propagation'; import { Avatar, Emoji, HStack, Icon, IconButton, Stack, Text } from './ui'; import type { Account as AccountEntity } from 'soapbox/types/entities'; @@ -22,6 +21,8 @@ const InstanceFavicon: React.FC = ({ account }) => { const history = useHistory(); const handleClick: React.MouseEventHandler = (e) => { + e.stopPropagation(); + const timelineUrl = `/timeline/${account.domain}`; if (!(e.ctrlKey || e.metaKey)) { history.push(timelineUrl); @@ -166,100 +167,106 @@ const Account = ({ const LinkEl: any = withLinkToProfile ? Link : 'div'; return ( - -
- - +
+ + + {children}} + > + event.stopPropagation()} + > + + {emoji && ( + + )} + + + +
{children}} + wrapper={(children) => {children}} > - - - {emoji && ( - event.stopPropagation()} + > +
+ - )} + + {account.verified && } +
-
- {children}} - > - -
- + + + @{username} - {account.verified && } -
-
-
- - - - @{username} - - {account.favicon && ( - - )} - - {(timestamp) ? ( - <> - · - - {timestampUrl ? ( - - - - ) : ( - - )} - - ) : null} - - {showEdit ? ( - <> - · - - - - ) : null} - - {actionType === 'muting' && account.mute_expires_at ? ( - <> - · - - - - ) : null} - - - {withAccountNote && ( - + {account.favicon && ( + )} - -
- -
- {withRelationship ? renderAction() : null} + {(timestamp) ? ( + <> + · + + {timestampUrl ? ( + event.stopPropagation()}> + + + ) : ( + + )} + + ) : null} + + {showEdit ? ( + <> + · + + + + ) : null} + + {actionType === 'muting' && account.mute_expires_at ? ( + <> + · + + + + ) : null} + + + {withAccountNote && ( + + )} +
-
- + +
+ {withRelationship ? renderAction() : null} +
+
+
); }; diff --git a/app/soapbox/components/polls/poll.tsx b/app/soapbox/components/polls/poll.tsx index 2df985b7e..2b6f99392 100644 --- a/app/soapbox/components/polls/poll.tsx +++ b/app/soapbox/components/polls/poll.tsx @@ -5,7 +5,6 @@ import { openModal } from 'soapbox/actions/modals'; import { vote } from 'soapbox/actions/polls'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; -import StopPropagation from '../stop-propagation'; import { Stack, Text } from '../ui'; import PollFooter from './poll-footer'; @@ -65,7 +64,8 @@ const Poll: React.FC = ({ id, status }): JSX.Element | null => { const showResults = poll.voted || poll.expired; return ( - + // eslint-disable-next-line jsx-a11y/no-static-element-interactions +
e.stopPropagation()}> {!showResults && poll.multiple && ( {intl.formatMessage(messages.multiple)} @@ -93,7 +93,7 @@ const Poll: React.FC = ({ id, status }): JSX.Element | null => { selected={selected} /> - +
); }; diff --git a/app/soapbox/components/quoted-status.tsx b/app/soapbox/components/quoted-status.tsx index 925f2115e..2c976bf86 100644 --- a/app/soapbox/components/quoted-status.tsx +++ b/app/soapbox/components/quoted-status.tsx @@ -13,7 +13,6 @@ import OutlineBox from './outline-box'; import StatusContent from './status-content'; import StatusReplyMentions from './status-reply-mentions'; import SensitiveContentOverlay from './statuses/sensitive-content-overlay'; -import StopPropagation from './stop-propagation'; import type { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities'; @@ -92,60 +91,58 @@ const QuotedStatus: React.FC = ({ status, onCancel, compose }) => } return ( - - + + + + + - + {(status.hidden) && ( + + )} - + + - - {(status.hidden) && ( - 0) && ( + )} - - - - - {(status.card || status.media_attachments.size > 0) && ( - - )} - - - + + ); }; diff --git a/app/soapbox/components/status-action-bar.tsx b/app/soapbox/components/status-action-bar.tsx index 54c181c71..011fc63a8 100644 --- a/app/soapbox/components/status-action-bar.tsx +++ b/app/soapbox/components/status-action-bar.tsx @@ -1,3 +1,4 @@ +import classNames from 'clsx'; import { List as ImmutableList } from 'immutable'; import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; @@ -15,7 +16,6 @@ import { initReport } from 'soapbox/actions/reports'; import { deleteStatus, editStatus, toggleMuteStatus } from 'soapbox/actions/statuses'; import EmojiButtonWrapper from 'soapbox/components/emoji-button-wrapper'; import StatusActionButton from 'soapbox/components/status-action-button'; -import { HStack } from 'soapbox/components/ui'; import DropdownMenuContainer from 'soapbox/containers/dropdown-menu-container'; import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount, useSettings, useSoapboxConfig } from 'soapbox/hooks'; import { isLocal } from 'soapbox/utils/accounts'; @@ -127,6 +127,8 @@ const StatusActionBar: React.FC = ({ } else { onOpenUnauthorizedModal('REPLY'); } + + e.stopPropagation(); }; const handleShareClick = () => { @@ -144,13 +146,18 @@ const StatusActionBar: React.FC = ({ } else { onOpenUnauthorizedModal('FAVOURITE'); } + + e.stopPropagation(); }; const handleBookmarkClick: React.EventHandler = (e) => { + e.stopPropagation(); dispatch(toggleBookmark(status)); }; const handleReblogClick: React.EventHandler = e => { + e.stopPropagation(); + if (me) { const modalReblog = () => dispatch(toggleReblog(status)); const boostModal = settings.get('boostModal'); @@ -165,6 +172,8 @@ const StatusActionBar: React.FC = ({ }; const handleQuoteClick: React.EventHandler = (e) => { + e.stopPropagation(); + if (me) { dispatch(quoteCompose(status)); } else { @@ -190,10 +199,12 @@ const StatusActionBar: React.FC = ({ }; const handleDeleteClick: React.EventHandler = (e) => { + e.stopPropagation(); doDeleteStatus(); }; const handleRedraftClick: React.EventHandler = (e) => { + e.stopPropagation(); doDeleteStatus(true); }; @@ -202,29 +213,35 @@ const StatusActionBar: React.FC = ({ }; const handlePinClick: React.EventHandler = (e) => { + e.stopPropagation(); dispatch(togglePin(status)); }; const handleMentionClick: React.EventHandler = (e) => { + e.stopPropagation(); dispatch(mentionCompose(status.account as Account)); }; const handleDirectClick: React.EventHandler = (e) => { + e.stopPropagation(); dispatch(directCompose(status.account as Account)); }; const handleChatClick: React.EventHandler = (e) => { + e.stopPropagation(); const account = status.account as Account; dispatch(launchChat(account.id, history)); }; const handleMuteClick: React.EventHandler = (e) => { + e.stopPropagation(); dispatch(initMuteModal(status.account as Account)); }; const handleBlockClick: React.EventHandler = (e) => { - const account = status.get('account') as Account; + e.stopPropagation(); + const account = status.get('account') as Account; dispatch(openModal('CONFIRM', { icon: require('@tabler/icons/ban.svg'), heading: , @@ -240,6 +257,7 @@ const StatusActionBar: React.FC = ({ }; const handleOpen: React.EventHandler = (e) => { + e.stopPropagation(); history.push(`/@${status.getIn(['account', 'acct'])}/posts/${status.id}`); }; @@ -251,10 +269,12 @@ const StatusActionBar: React.FC = ({ }; const handleReport: React.EventHandler = (e) => { + e.stopPropagation(); dispatch(initReport(status.account as Account, status)); }; const handleConversationMuteClick: React.EventHandler = (e) => { + e.stopPropagation(); dispatch(toggleMuteStatus(status)); }; @@ -262,6 +282,8 @@ const StatusActionBar: React.FC = ({ const { uri } = status; const textarea = document.createElement('textarea'); + e.stopPropagation(); + textarea.textContent = uri; textarea.style.position = 'fixed'; @@ -278,15 +300,18 @@ const StatusActionBar: React.FC = ({ }; const onModerate: React.MouseEventHandler = (e) => { + e.stopPropagation(); const account = status.account as Account; dispatch(openModal('ACCOUNT_MODERATION', { accountId: account.id })); }; const handleDeleteStatus: React.EventHandler = (e) => { + e.stopPropagation(); dispatch(deleteStatusModal(intl, status.id)); }; const handleToggleStatusSensitivity: React.EventHandler = (e) => { + e.stopPropagation(); dispatch(toggleStatusSensitivityModal(intl, status.id, status.sensitive)); }; @@ -525,77 +550,74 @@ const StatusActionBar: React.FC = ({ const canShare = ('share' in navigator) && status.visibility === 'public'; return ( - - e.stopPropagation()} - onMouseDown={e => e.stopPropagation()} - onClick={e => e.stopPropagation()} - > - +
+ - {(features.quotePosts && me) ? ( - - {reblogButton} - - ) : ( - reblogButton - )} + {(features.quotePosts && me) ? ( + + {reblogButton} + + ) : ( + reblogButton + )} - {features.emojiReacts ? ( - - - - ) : ( + {features.emojiReacts ? ( + - )} + + ) : ( + + )} - {canShare && ( - - )} + {canShare && ( + + )} - - - - - + + + +
); }; diff --git a/app/soapbox/components/status-content.tsx b/app/soapbox/components/status-content.tsx index 2b2754176..a495a9f82 100644 --- a/app/soapbox/components/status-content.tsx +++ b/app/soapbox/components/status-content.tsx @@ -12,7 +12,6 @@ import { isRtl } from '../rtl'; import Poll from './polls/poll'; import './status-content.css'; -import StopPropagation from './stop-propagation'; import type { Status, Mention } from 'soapbox/types/entities'; @@ -30,12 +29,10 @@ interface IReadMoreButton { /** Button to expand a truncated status (due to too much content) */ const ReadMoreButton: React.FC = ({ onClick }) => ( - - - + ); interface IStatusContent { @@ -106,10 +103,6 @@ const StatusContent: React.FC = ({ status, onClick, collapsable link.setAttribute('title', link.href); link.addEventListener('click', onLinkClick.bind(link), false); } - - // Prevent bubbling - link.addEventListener('mouseup', e => e.stopPropagation()); - link.addEventListener('mousedown', e => e.stopPropagation()); }); }; diff --git a/app/soapbox/components/status-media.tsx b/app/soapbox/components/status-media.tsx index f3d418d30..71177cb12 100644 --- a/app/soapbox/components/status-media.tsx +++ b/app/soapbox/components/status-media.tsx @@ -2,7 +2,6 @@ import React, { useState } from 'react'; import { openModal } from 'soapbox/actions/modals'; import AttachmentThumbs from 'soapbox/components/attachment-thumbs'; -import StopPropagation from 'soapbox/components/stop-propagation'; import PlaceholderCard from 'soapbox/features/placeholder/components/placeholder-card'; import Card from 'soapbox/features/status/components/card'; import Bundle from 'soapbox/features/ui/components/bundle'; @@ -174,15 +173,7 @@ const StatusMedia: React.FC = ({ ); } - if (media) { - return ( - - {media} - - ); - } else { - return null; - } + return media; }; export default StatusMedia; diff --git a/app/soapbox/components/status-reply-mentions.tsx b/app/soapbox/components/status-reply-mentions.tsx index 3adde6b65..0964c84ae 100644 --- a/app/soapbox/components/status-reply-mentions.tsx +++ b/app/soapbox/components/status-reply-mentions.tsx @@ -5,7 +5,6 @@ import { Link } from 'react-router-dom'; import { openModal } from 'soapbox/actions/modals'; import HoverRefWrapper from 'soapbox/components/hover-ref-wrapper'; import HoverStatusWrapper from 'soapbox/components/hover-status-wrapper'; -import StopPropagation from 'soapbox/components/stop-propagation'; import { useAppDispatch } from 'soapbox/hooks'; import type { Account, Status } from 'soapbox/types/entities'; @@ -19,6 +18,8 @@ const StatusReplyMentions: React.FC = ({ status, hoverable const dispatch = useAppDispatch(); const handleOpenMentionsModal: React.MouseEventHandler = (e) => { + e.stopPropagation(); + const account = status.account as Account; dispatch(openModal('MENTIONS', { @@ -49,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) { @@ -72,34 +73,32 @@ const StatusReplyMentions: React.FC = ({ status, hoverable } return ( - -
- , - hover: (children: React.ReactNode) => { - if (hoverable) { - return ( - - - {children} - - - ); - } else { - return children; - } - }, - }} - /> -
-
+
+ , + hover: (children: React.ReactNode) => { + if (hoverable) { + return ( + + + {children} + + + ); + } else { + return children; + } + }, + }} + /> +
); }; diff --git a/app/soapbox/components/status.tsx b/app/soapbox/components/status.tsx index 31af2cbaa..846735dc3 100644 --- a/app/soapbox/components/status.tsx +++ b/app/soapbox/components/status.tsx @@ -235,8 +235,7 @@ const Status: React.FC = (props) => { reblogElement = ( e.stopPropagation()} - onMouseUp={e => e.stopPropagation()} + onClick={(event) => event.stopPropagation()} className='hidden sm:flex items-center text-gray-700 dark:text-gray-600 text-xs font-medium space-x-1 hover:underline' > @@ -259,8 +258,7 @@ const Status: React.FC = (props) => {
e.stopPropagation()} - onMouseUp={e => e.stopPropagation()} + onClick={(event) => event.stopPropagation()} className='flex items-center text-gray-700 dark:text-gray-600 text-xs font-medium space-x-1 hover:underline' > diff --git a/app/soapbox/components/statuses/sensitive-content-overlay.tsx b/app/soapbox/components/statuses/sensitive-content-overlay.tsx index 21411bf5c..59faef161 100644 --- a/app/soapbox/components/statuses/sensitive-content-overlay.tsx +++ b/app/soapbox/components/statuses/sensitive-content-overlay.tsx @@ -5,7 +5,6 @@ import { defineMessages, useIntl } from 'react-intl'; import { useSettings, useSoapboxConfig } from 'soapbox/hooks'; import { defaultMediaVisibility } from 'soapbox/utils/status'; -import StopPropagation from '../stop-propagation'; import { Button, HStack, Text } from '../ui'; import type { Status as StatusEntity } from 'soapbox/types/entities'; @@ -39,7 +38,9 @@ const SensitiveContentOverlay = React.forwardRef(defaultMediaVisibility(status, displayMedia)); - const toggleVisibility = () => { + const toggleVisibility = (event: React.MouseEvent) => { + event.stopPropagation(); + if (onToggleVisibility) { onToggleVisibility(); } else { @@ -63,15 +64,13 @@ const SensitiveContentOverlay = React.forwardRef {visible ? ( - - - - )} - - ) : null} + {isUnderReview ? ( + <> + {links.get('support') && ( + event.stopPropagation()} + > + + + )} + + ) : null} - - +
)} diff --git a/app/soapbox/components/stop-propagation.tsx b/app/soapbox/components/stop-propagation.tsx deleted file mode 100644 index f8eff7b4e..000000000 --- a/app/soapbox/components/stop-propagation.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; - -interface IStopPropagation { - /** Children to render within the bubble. */ - children: React.ReactNode, - /** Whether to prevent mouse events from bubbling. (default: `true`) */ - enabled?: boolean, -} - -/** - * Prevent mouse events from bubbling up. - * - * Why is this needed? Because `onClick`, `onMouseDown`, and `onMouseUp` are 3 separate events. - * To prevent a lot of code duplication, this component can stop all mouse events. - * Plus, placing it in the component tree makes it more readable. - */ -const StopPropagation: React.FC = ({ children, enabled = true }) => { - - const handler: React.MouseEventHandler = (e) => { - if (enabled) { - e.stopPropagation(); - } - }; - - return ( - // eslint-disable-next-line jsx-a11y/no-static-element-interactions -
- {children} -
- ); -}; - -export default StopPropagation; \ No newline at end of file diff --git a/app/soapbox/components/translate-button.tsx b/app/soapbox/components/translate-button.tsx index 7c40a4a14..07c778fd2 100644 --- a/app/soapbox/components/translate-button.tsx +++ b/app/soapbox/components/translate-button.tsx @@ -4,7 +4,6 @@ import { FormattedMessage, useIntl } from 'react-intl'; import { translateStatus, undoStatusTranslation } from 'soapbox/actions/statuses'; import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; -import StopPropagation from './stop-propagation'; import { Stack } from './ui'; import type { Status } from 'soapbox/types/entities'; @@ -43,21 +42,17 @@ const TranslateButton: React.FC = ({ status }) => { - - - + ); } return ( - - - + ); }; diff --git a/app/soapbox/components/ui/button/button.tsx b/app/soapbox/components/ui/button/button.tsx index 1b0528efb..97de8862b 100644 --- a/app/soapbox/components/ui/button/button.tsx +++ b/app/soapbox/components/ui/button/button.tsx @@ -8,7 +8,7 @@ import { useButtonStyles } from './useButtonStyles'; import type { ButtonSizes, ButtonThemes } from './useButtonStyles'; -interface IButton extends Pick, 'onClick' | 'onMouseUp'> { +interface IButton { /** Whether this button expands the width of its container. */ block?: boolean, /** Elements inside the