diff --git a/app/javascript/flavours/glitch/components/alt_text_badge.tsx b/app/javascript/flavours/glitch/components/alt_text_badge.tsx index 293d398b3c..9b3748b2ca 100644 --- a/app/javascript/flavours/glitch/components/alt_text_badge.tsx +++ b/app/javascript/flavours/glitch/components/alt_text_badge.tsx @@ -13,9 +13,9 @@ import { useSelectableClick } from 'flavours/glitch/hooks/useSelectableClick'; const offset = [0, 4] as OffsetValue; const popperConfig = { strategy: 'fixed' } as UsePopperOptions; -export const AltTextBadge: React.FC<{ - description: string; -}> = ({ description }) => { +export const AltTextBadge: React.FC<{ description: string }> = ({ + description, +}) => { const accessibilityId = useId(); const anchorRef = useRef(null); const [open, setOpen] = useState(false); @@ -56,7 +56,7 @@ export const AltTextBadge: React.FC<{ {({ props }) => (
> = ({ - id, - children, -}) => { - const dismissed = useAppSelector((state) => +export function useDismissableBannerState({ id }: Props) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const dismissed: boolean = useAppSelector((state) => + /* eslint-disable-next-line */ state.settings.getIn(['dismissed_banners', id], false), ); + + const [isVisible, setIsVisible] = useState( + !bannerSettings.get(id) && !dismissed, + ); + const dispatch = useAppDispatch(); - const [visible, setVisible] = useState(!bannerSettings.get(id) && !dismissed); - const intl = useIntl(); - - const handleDismiss = useCallback(() => { - setVisible(false); + const dismiss = useCallback(() => { + setIsVisible(false); bannerSettings.set(id, true); dispatch(changeSetting(['dismissed_banners', id], true)); }, [id, dispatch]); useEffect(() => { - if (!visible && !dismissed) { + // Store legacy localStorage setting on server + if (!isVisible && !dismissed) { dispatch(changeSetting(['dismissed_banners', id], true)); } - }, [id, dispatch, visible, dismissed]); + }, [id, dispatch, isVisible, dismissed]); - if (!visible) { + return { + isVisible, + dismiss, + }; +} + +export const DismissableBanner: React.FC> = ({ + id, + children, +}) => { + const intl = useIntl(); + const { isVisible, dismiss } = useDismissableBannerState({ + id, + }); + + if (!isVisible) { return null; } @@ -58,7 +70,7 @@ export const DismissableBanner: React.FC> = ({ icon='times' iconComponent={CloseIcon} title={intl.formatMessage(messages.dismiss)} - onClick={handleDismiss} + onClick={dismiss} />
diff --git a/app/javascript/flavours/glitch/components/status_action_bar.jsx b/app/javascript/flavours/glitch/components/status_action_bar/index.jsx similarity index 92% rename from app/javascript/flavours/glitch/components/status_action_bar.jsx rename to app/javascript/flavours/glitch/components/status_action_bar/index.jsx index 2cbce0053d..7c79e999f3 100644 --- a/app/javascript/flavours/glitch/components/status_action_bar.jsx +++ b/app/javascript/flavours/glitch/components/status_action_bar/index.jsx @@ -22,12 +22,13 @@ import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { Dropdown } from 'flavours/glitch/components/dropdown_menu'; -import { me } from '../initial_state'; +import { me } from '../../initial_state'; -import { IconButton } from './icon_button'; -import { RelativeTimestamp } from './relative_timestamp'; -import { isFeatureEnabled } from '../utils/environment'; -import { ReblogButton } from './status/reblog_button'; +import { IconButton } from '../icon_button'; +import { RelativeTimestamp } from '../relative_timestamp'; +import { isFeatureEnabled } from '../../utils/environment'; +import { ReblogButton } from '../status/reblog_button'; +import { RemoveQuoteHint } from './remove_quote_hint'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, @@ -76,6 +77,7 @@ class StatusActionBar extends ImmutablePureComponent { identity: identityContextPropShape, status: ImmutablePropTypes.map.isRequired, quotedAccountId: PropTypes.string, + contextType: PropTypes.string, onReply: PropTypes.func, onFavourite: PropTypes.func, onDelete: PropTypes.func, @@ -213,7 +215,7 @@ class StatusActionBar extends ImmutablePureComponent { }; render () { - const { status, quotedAccountId, intl, withDismiss, withCounters, showReplyCount, scrollKey } = this.props; + const { status, quotedAccountId, contextType, intl, withDismiss, withCounters, showReplyCount, scrollKey } = this.props; const { signedIn, permissions } = this.props.identity; const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); @@ -221,6 +223,7 @@ class StatusActionBar extends ImmutablePureComponent { const mutingConversation = status.get('muted'); const writtenByMe = status.getIn(['account', 'id']) === me; const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']); + const isQuotingMe = quotedAccountId === me; let menu = []; let reblogIcon = 'retweet'; @@ -271,7 +274,7 @@ class StatusActionBar extends ImmutablePureComponent { menu.push(null); } - if (quotedAccountId === me) { + if (isQuotingMe) { menu.push({ text: intl.formatMessage(messages.revokeQuote, { name: status.getIn(['account', 'username']) }), action: this.handleRevokeQuoteClick, dangerous: true }); } @@ -320,6 +323,8 @@ 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 shouldShowQuoteRemovalHint = isQuotingMe && contextType === 'notifications'; + return (
@@ -345,18 +350,24 @@ class StatusActionBar extends ImmutablePureComponent { {filterButton} -
- -
+ + {(dismissQuoteHint) => ( + { + dismissQuoteHint(); + return true; + }} + /> + )} +
diff --git a/app/javascript/flavours/glitch/components/status_action_bar/remove_quote_hint.tsx b/app/javascript/flavours/glitch/components/status_action_bar/remove_quote_hint.tsx new file mode 100644 index 0000000000..6046dad035 --- /dev/null +++ b/app/javascript/flavours/glitch/components/status_action_bar/remove_quote_hint.tsx @@ -0,0 +1,90 @@ +import { useRef } from 'react'; + +import { FormattedMessage, useIntl } from 'react-intl'; + +import classNames from 'classnames'; + +import Overlay from 'react-overlays/Overlay'; + +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; + +import { Button } from '../button'; +import { useDismissableBannerState } from '../dismissable_banner'; +import { Icon } from '../icon'; + +const DISMISSABLE_BANNER_ID = 'notifications/remove_quote_hint'; + +export const RemoveQuoteHint: React.FC<{ + canShowHint: boolean; + className?: string; + children: (dismiss: () => void) => React.ReactNode; +}> = ({ canShowHint, className, children }) => { + const anchorRef = useRef(null); + const intl = useIntl(); + + const { isVisible, dismiss } = useDismissableBannerState({ + id: DISMISSABLE_BANNER_ID, + }); + + return ( +
+ {children(dismiss)} + {isVisible && canShowHint && ( + + {({ props, placement }) => ( +
+

+ +

+ + ), + }} + > + {(text) =>

{text}

} +
+ + {(text) => ( + + )} + +
+ )} +
+ )} +
+ ); +}; diff --git a/app/javascript/flavours/glitch/reducers/settings.js b/app/javascript/flavours/glitch/reducers/settings.js index 6e83294167..86d5e614df 100644 --- a/app/javascript/flavours/glitch/reducers/settings.js +++ b/app/javascript/flavours/glitch/reducers/settings.js @@ -124,6 +124,7 @@ const initialState = ImmutableMap({ 'explore/links': false, 'explore/statuses': false, 'explore/tags': false, + 'notifications/remove_quote_hint': false, }), }); diff --git a/app/javascript/flavours/glitch/styles/components.scss b/app/javascript/flavours/glitch/styles/components.scss index 82336367ea..d708f1cd56 100644 --- a/app/javascript/flavours/glitch/styles/components.scss +++ b/app/javascript/flavours/glitch/styles/components.scss @@ -471,8 +471,8 @@ } } -body > [data-popper-placement] { - z-index: 3; +[data-popper-placement] { + z-index: 9999; } .invisible { @@ -7395,7 +7395,8 @@ img.modal-warning { cursor: default; } -.media-gallery__alt__popover { +.info-tooltip { + color: $white; background: color.change($black, $alpha: 0.65); backdrop-filter: $backdrop-blur-filter; border-radius: 4px; @@ -7407,20 +7408,36 @@ img.modal-warning { max-height: 30em; overflow-y: auto; + &--solid { + color: var(--nested-card-text); + background: + /* This is a bit of a silly hack for layering two background colours + * since --nested-card-background is too transparent for a tooltip */ + linear-gradient( + var(--nested-card-background), + var(--nested-card-background) + ), + linear-gradient(var(--background-color), var(--background-color)); + border: var(--nested-card-border); + } + h4 { font-size: 15px; line-height: 20px; font-weight: 500; - color: $white; margin-bottom: 8px; } p { font-size: 15px; line-height: 20px; - color: color.change($white, $alpha: 0.85); + opacity: 0.85; white-space: pre-line; } + + .button { + margin-block-start: 8px; + } } .attachment-list {