diff --git a/app/javascript/mastodon/components/content_warning.tsx b/app/javascript/mastodon/components/content_warning.tsx new file mode 100644 index 00000000000..8498e3ccc66 --- /dev/null +++ b/app/javascript/mastodon/components/content_warning.tsx @@ -0,0 +1,30 @@ +import { FormattedMessage } from 'react-intl'; + +export const ContentWarning: React.FC<{ + text: string; + expanded?: boolean; + onClick?: () => void; +}> = ({ text, expanded, onClick }) => ( +
+
+ +
+

{text}

+ +
+ +
+
+); diff --git a/app/javascript/mastodon/features/notifications_v2/components/embedded_status.tsx b/app/javascript/mastodon/features/notifications_v2/components/embedded_status.tsx index baec0161173..0dbf5dbf8da 100644 --- a/app/javascript/mastodon/features/notifications_v2/components/embedded_status.tsx +++ b/app/javascript/mastodon/features/notifications_v2/components/embedded_status.tsx @@ -1,4 +1,4 @@ -import { useCallback, useRef } from 'react'; +import { useCallback, useRef, useState } from 'react'; import { FormattedMessage } from 'react-intl'; @@ -9,6 +9,7 @@ import type { List as ImmutableList, RecordOf } from 'immutable'; import BarChart4BarsIcon from '@/material-icons/400-24px/bar_chart_4_bars.svg?react'; import PhotoLibraryIcon from '@/material-icons/400-24px/photo_library.svg?react'; import { Avatar } from 'mastodon/components/avatar'; +import { ContentWarning } from 'mastodon/components/content_warning'; import { DisplayName } from 'mastodon/components/display_name'; import { Icon } from 'mastodon/components/icon'; import type { Status } from 'mastodon/models/status'; @@ -23,6 +24,7 @@ export const EmbeddedStatus: React.FC<{ statusId: string }> = ({ }) => { const history = useHistory(); const clickCoordinatesRef = useRef<[number, number] | null>(); + const [expanded, setExpanded] = useState(false); const status = useAppSelector( (state) => state.statuses.get(statusId) as Status | undefined, @@ -96,12 +98,17 @@ export const EmbeddedStatus: React.FC<{ statusId: string }> = ({ [], ); + const handleContentWarningClick = useCallback(() => { + setExpanded((v) => !v); + }, [setExpanded]); + if (!status) { return null; } // Assign status attributes to variables with a forced type, as status is not yet properly typed const contentHtml = status.get('contentHtml') as string; + const contentWarning = status.get('spoiler_text') as string; const poll = status.get('poll'); const language = status.get('language') as string; const mentions = status.get('mentions') as ImmutableList; @@ -124,12 +131,22 @@ export const EmbeddedStatus: React.FC<{ statusId: string }> = ({
- + {contentWarning && ( + + )} + + {(!contentWarning || expanded) && ( + + )} {(poll || mediaAttachmentsSize > 0) && (
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 86afa7cd0d7..f4e0323a817 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -193,6 +193,8 @@ "confirmations.unfollow.confirm": "Unfollow", "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", "confirmations.unfollow.title": "Unfollow user?", + "content_warning.hide": "Hide post", + "content_warning.show": "Show anyway", "conversation.delete": "Delete conversation", "conversation.mark_as_read": "Mark as read", "conversation.open": "View conversation", diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 3c938ac4c59..d64901afd00 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -10438,39 +10438,53 @@ noscript { } &__embedded-status { + display: flex; + flex-direction: column; + gap: 8px; cursor: pointer; &__account { display: flex; align-items: center; gap: 4px; - margin-bottom: 8px; color: $dark-text-color; + font-size: 15px; + line-height: 22px; bdi { - color: inherit; + color: $darker-text-color; } } - .account__avatar { - opacity: 0.5; - } - &__content { display: -webkit-box; font-size: 15px; line-height: 22px; - color: $dark-text-color; + color: $darker-text-color; -webkit-line-clamp: 4; -webkit-box-orient: vertical; max-height: 4 * 22px; overflow: hidden; + p { + display: none; + + &:first-child { + display: initial; + } + } + p, a { color: inherit; } } + + .reply-indicator__attachments { + font-size: 15px; + line-height: 22px; + color: $dark-text-color; + } } } @@ -10740,3 +10754,45 @@ noscript { } } } + +.content-warning { + display: flex; + align-items: stretch; + gap: 8px; + background: rgba($ui-highlight-color, 0.05); + color: $secondary-text-color; + border-top: 1px solid; + border-bottom: 1px solid; + border-color: rgba($ui-highlight-color, 0.15); + + &__body { + flex: 1 1 auto; + padding: 8px 0; + font-size: 15px; + line-height: 22px; + + p { + margin-bottom: 8px; + } + + .link-button { + font-size: inherit; + line-height: inherit; + font-weight: 500; + } + } + + &__border { + background: url('../images/warning-stripes.svg') repeat-y; + width: 5px; + flex: 0 0 auto; + + &:first-child { + border-start-start-radius: 4px; + } + + &:last-child { + border-start-end-radius: 4px; + } + } +}