diff --git a/app/javascript/flavours/glitch/actions/accounts.js b/app/javascript/flavours/glitch/actions/accounts.js index bf9f813ef0..07f6683b43 100644 --- a/app/javascript/flavours/glitch/actions/accounts.js +++ b/app/javascript/flavours/glitch/actions/accounts.js @@ -153,7 +153,8 @@ export function fetchAccountFail(id, error) { */ export function followAccount(id, options = { reblogs: true }) { return (dispatch, getState) => { - const alreadyFollowing = getState().getIn(['relationships', id, 'following']); + const relationship = getState().getIn(['relationships', id]); + const alreadyFollowing = relationship?.following || relationship?.requested; const locked = getState().getIn(['accounts', id, 'locked'], false); dispatch(followAccountRequest({ id, locked })); diff --git a/app/javascript/flavours/glitch/components/dropdown_menu.tsx b/app/javascript/flavours/glitch/components/dropdown_menu.tsx index fa196a8bdb..1284d6ee13 100644 --- a/app/javascript/flavours/glitch/components/dropdown_menu.tsx +++ b/app/javascript/flavours/glitch/components/dropdown_menu.tsx @@ -71,10 +71,15 @@ export const DropdownMenuItemContent: React.FC<{ item: MenuItem }> = ({ return null; } - const { text, description, icon } = item; + const { text, description, icon, iconId } = item; return ( <> - {icon && } + {icon && ( + + )} {text} {Boolean(description) && ( diff --git a/app/javascript/flavours/glitch/components/follow_button.tsx b/app/javascript/flavours/glitch/components/follow_button.tsx index c06bfe4c77..6b5bffbd6f 100644 --- a/app/javascript/flavours/glitch/components/follow_button.tsx +++ b/app/javascript/flavours/glitch/components/follow_button.tsx @@ -5,6 +5,7 @@ import { useIntl, defineMessages } from 'react-intl'; import classNames from 'classnames'; import { useIdentity } from '@/flavours/glitch/identity_context'; +import { isClientFeatureEnabled } from '@/flavours/glitch/utils/environment'; import { fetchRelationships, followAccount, @@ -94,7 +95,17 @@ export const FollowButton: React.FC<{ if (accountId === me) { return; - } else if (relationship.muting) { + } else if (relationship.blocking) { + dispatch( + openModal({ + modalType: 'CONFIRM_UNBLOCK', + modalProps: { account }, + }), + ); + } else if ( + relationship.muting && + !isClientFeatureEnabled('profile_redesign') + ) { dispatch(unmuteAccount(accountId)); } else if (account && relationship.following) { dispatch( @@ -107,13 +118,6 @@ export const FollowButton: React.FC<{ modalProps: { account }, }), ); - } else if (relationship.blocking) { - dispatch( - openModal({ - modalType: 'CONFIRM_UNBLOCK', - modalProps: { account }, - }), - ); } else { dispatch(followAccount(accountId)); } @@ -136,7 +140,10 @@ export const FollowButton: React.FC<{ label = intl.formatMessage(messages.edit_profile); } else if (!relationship) { label = ; - } else if (relationship.muting) { + } else if ( + relationship.muting && + !isClientFeatureEnabled('profile_redesign') + ) { label = intl.formatMessage(messages.unmute); } else if (relationship.following) { label = intl.formatMessage(messages.unfollow); @@ -173,7 +180,7 @@ export const FollowButton: React.FC<{ (!(relationship?.following || relationship?.requested) && (account?.suspended || !!account?.moved)) } - secondary={following} + secondary={following || relationship?.blocking} compact={compact} className={classNames(className, { 'button--destructive': following })} > diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/account_header.tsx b/app/javascript/flavours/glitch/features/account_timeline/components/account_header.tsx index a19a1d3550..dc57350e7b 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/account_header.tsx +++ b/app/javascript/flavours/glitch/features/account_timeline/components/account_header.tsx @@ -1,4 +1,5 @@ -import { useCallback } from 'react'; +import type { RefCallback } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import classNames from 'classnames'; import { Helmet } from 'react-helmet'; @@ -81,6 +82,40 @@ export const AccountHeader: React.FC<{ [dispatch, account], ); + const [isFooterIntersecting, setIsIntersecting] = useState(false); + const handleIntersect: IntersectionObserverCallback = useCallback( + (entries) => { + const entry = entries.at(0); + if (!entry) { + return; + } + + setIsIntersecting(entry.isIntersecting); + }, + [], + ); + const [observer] = useState( + () => + new IntersectionObserver(handleIntersect, { + rootMargin: '0px 0px -55px 0px', // Height of bottom nav bar. + }), + ); + + const handleObserverRef: RefCallback = useCallback( + (node) => { + if (node) { + observer.observe(node); + } + }, + [observer], + ); + + useEffect(() => { + return () => { + observer.disconnect(); + }; + }, [observer]); + if (!account) { return null; } @@ -118,7 +153,12 @@ export const AccountHeader: React.FC<{ )} - {!hideTabs && !hidden && } +
{titleFromAccount(account)} diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/badges.tsx b/app/javascript/flavours/glitch/features/account_timeline/components/badges.tsx index 6d0793bd25..de54dfe473 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/badges.tsx +++ b/app/javascript/flavours/glitch/features/account_timeline/components/badges.tsx @@ -108,48 +108,6 @@ export const AccountBadges: FC<{ accountId: string }> = ({ accountId }) => { className={classNames(className, classes.badgeMuted)} />, ); - } else if ( - relationship.followed_by && - (relationship.following || relationship.requested) - ) { - badges.push( - - } - className={className} - />, - ); - } else if (relationship.followed_by) { - badges.push( - - } - className={className} - />, - ); - } else if (relationship.requested_by) { - badges.push( - - } - className={className} - />, - ); } } diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/menu.tsx b/app/javascript/flavours/glitch/features/account_timeline/components/menu.tsx index 5f76a944fe..590f5d1fa6 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/menu.tsx +++ b/app/javascript/flavours/glitch/features/account_timeline/components/menu.tsx @@ -12,6 +12,7 @@ import { unpinAccount, } from '@/flavours/glitch/actions/accounts'; import { removeAccountFromFollowers } from '@/flavours/glitch/actions/accounts_typed'; +import { showAlert } from '@/flavours/glitch/actions/alerts'; import { directCompose, mentionCompose, @@ -26,16 +27,78 @@ import { initReport } from '@/flavours/glitch/actions/reports'; import { Dropdown } from '@/flavours/glitch/components/dropdown_menu'; import { useAccount } from '@/flavours/glitch/hooks/useAccount'; import { useIdentity } from '@/flavours/glitch/identity_context'; +import type { Account } from '@/flavours/glitch/models/account'; import type { MenuItem } from '@/flavours/glitch/models/dropdown_menu'; +import type { Relationship } from '@/flavours/glitch/models/relationship'; import { PERMISSION_MANAGE_FEDERATION, PERMISSION_MANAGE_USERS, } from '@/flavours/glitch/permissions'; +import type { AppDispatch } from '@/flavours/glitch/store'; import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store'; +import BlockIcon from '@/material-icons/400-24px/block.svg?react'; +import EditIcon from '@/material-icons/400-24px/edit_square.svg?react'; +import LinkIcon from '@/material-icons/400-24px/link_2.svg?react'; import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; +import PersonRemoveIcon from '@/material-icons/400-24px/person_remove.svg?react'; +import ReportIcon from '@/material-icons/400-24px/report.svg?react'; +import ShareIcon from '@/material-icons/400-24px/share.svg?react'; import { isRedesignEnabled } from '../common'; +export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => { + const intl = useIntl(); + const { signedIn, permissions } = useIdentity(); + + const account = useAccount(accountId); + const relationship = useAppSelector((state) => + state.relationships.get(accountId), + ); + + const dispatch = useAppDispatch(); + const menuItems = useMemo(() => { + if (!account) { + return []; + } + + if (isRedesignEnabled()) { + return redesignMenuItems({ + account, + signedIn, + permissions, + intl, + relationship, + dispatch, + }); + } + return currentMenuItems({ + account, + signedIn, + permissions, + intl, + relationship, + dispatch, + }); + }, [account, signedIn, permissions, intl, relationship, dispatch]); + return ( + + ); +}; + +interface MenuItemsParams { + account: Account; + signedIn: boolean; + permissions: number; + intl: ReturnType; + relationship?: Relationship; + dispatch: AppDispatch; +} + const messages = defineMessages({ unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' }, @@ -112,80 +175,78 @@ const messages = defineMessages({ }, }); -export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => { - const intl = useIntl(); - const { signedIn, permissions } = useIdentity(); +function currentMenuItems({ + account, + signedIn, + permissions, + intl, + relationship, + dispatch, +}: MenuItemsParams): MenuItem[] { + const items: MenuItem[] = []; + const isRemote = account.acct !== account.username; - const account = useAccount(accountId); - const relationship = useAppSelector((state) => - state.relationships.get(accountId), - ); - - const dispatch = useAppDispatch(); - const menuItems = useMemo(() => { - const arr: MenuItem[] = []; - - if (!account) { - return arr; - } - - const isRemote = account.acct !== account.username; - - if (signedIn && !account.suspended) { - arr.push({ + if (signedIn && !account.suspended) { + items.push( + { text: intl.formatMessage(messages.mention, { name: account.username, }), action: () => { dispatch(mentionCompose(account)); }, - }); - arr.push({ + }, + { text: intl.formatMessage(messages.direct, { name: account.username, }), action: () => { dispatch(directCompose(account)); }, - }); - arr.push(null); - } + }, + null, + ); + } - if (isRemote) { - arr.push({ + if (isRemote) { + items.push( + { text: intl.formatMessage(messages.openOriginalPage), href: account.url, - }); - arr.push(null); - } + }, + null, + ); + } - if (!signedIn) { - return arr; - } + if (!signedIn) { + return items; + } - if (relationship?.following) { - if (!relationship.muting) { - if (relationship.showing_reblogs) { - arr.push({ - text: intl.formatMessage(messages.hideReblogs, { - name: account.username, - }), - action: () => { - dispatch(followAccount(account.id, { reblogs: false })); - }, - }); - } else { - arr.push({ - text: intl.formatMessage(messages.showReblogs, { - name: account.username, - }), - action: () => { - dispatch(followAccount(account.id, { reblogs: true })); - }, - }); - } + if (relationship?.following) { + // Timeline options + if (!relationship.muting) { + if (relationship.showing_reblogs) { + items.push({ + text: intl.formatMessage(messages.hideReblogs, { + name: account.username, + }), + action: () => { + dispatch(followAccount(account.id, { reblogs: false })); + }, + }); + } else { + items.push({ + text: intl.formatMessage(messages.showReblogs, { + name: account.username, + }), + action: () => { + dispatch(followAccount(account.id, { reblogs: true })); + }, + }); + } - arr.push({ + items.push( + { text: intl.formatMessage(messages.languages), action: () => { dispatch( @@ -197,13 +258,295 @@ export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => { }), ); }, - }); - arr.push(null); - } + }, + null, + ); } - if (isRedesignEnabled()) { - arr.push({ + items.push( + { + text: intl.formatMessage( + relationship.endorsed ? messages.unendorse : messages.endorse, + ), + action: () => { + if (relationship.endorsed) { + dispatch(unpinAccount(account.id)); + } else { + dispatch(pinAccount(account.id)); + } + }, + }, + { + text: intl.formatMessage(messages.add_or_remove_from_list), + action: () => { + dispatch( + openModal({ + modalType: 'LIST_ADDER', + modalProps: { + accountId: account.id, + }, + }), + ); + }, + }, + null, + ); + } + + if (relationship?.followed_by) { + const handleRemoveFromFollowers = () => { + dispatch( + openModal({ + modalType: 'CONFIRM', + modalProps: { + title: intl.formatMessage(messages.confirmRemoveFromFollowersTitle), + message: intl.formatMessage( + messages.confirmRemoveFromFollowersMessage, + { name: {account.acct} }, + ), + confirm: intl.formatMessage( + messages.confirmRemoveFromFollowersButton, + ), + onConfirm: () => { + void dispatch( + removeAccountFromFollowers({ accountId: account.id }), + ); + }, + }, + }), + ); + }; + + items.push({ + text: intl.formatMessage(messages.removeFromFollowers, { + name: account.username, + }), + action: handleRemoveFromFollowers, + dangerous: true, + }); + } + + if (relationship?.muting) { + items.push({ + text: intl.formatMessage(messages.unmute, { + name: account.username, + }), + action: () => { + dispatch(unmuteAccount(account.id)); + }, + }); + } else { + items.push({ + text: intl.formatMessage(messages.mute, { + name: account.username, + }), + action: () => { + dispatch(initMuteModal(account)); + }, + dangerous: true, + }); + } + + if (relationship?.blocking) { + items.push({ + text: intl.formatMessage(messages.unblock, { + name: account.username, + }), + action: () => { + dispatch(unblockAccount(account.id)); + }, + }); + } else { + items.push({ + text: intl.formatMessage(messages.block, { + name: account.username, + }), + action: () => { + dispatch(blockAccount(account.id)); + }, + dangerous: true, + }); + } + + if (!account.suspended) { + items.push({ + text: intl.formatMessage(messages.report, { + name: account.username, + }), + action: () => { + dispatch(initReport(account)); + }, + dangerous: true, + }); + } + + const remoteDomain = isRemote ? account.acct.split('@')[1] : null; + if (remoteDomain) { + items.push(null); + + if (relationship?.domain_blocking) { + items.push({ + text: intl.formatMessage(messages.unblockDomain, { + domain: remoteDomain, + }), + action: () => { + dispatch(unblockDomain(remoteDomain)); + }, + }); + } else { + items.push({ + text: intl.formatMessage(messages.blockDomain, { + domain: remoteDomain, + }), + action: () => { + dispatch(initDomainBlockModal(account)); + }, + dangerous: true, + }); + } + } + + if ( + (permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS || + (isRemote && + (permissions & PERMISSION_MANAGE_FEDERATION) === + PERMISSION_MANAGE_FEDERATION) + ) { + items.push(null); + if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) { + items.push({ + text: intl.formatMessage(messages.admin_account, { + name: account.username, + }), + href: `/admin/accounts/${account.id}`, + }); + } + if ( + isRemote && + (permissions & PERMISSION_MANAGE_FEDERATION) === + PERMISSION_MANAGE_FEDERATION + ) { + items.push({ + text: intl.formatMessage(messages.admin_domain, { + domain: remoteDomain, + }), + href: `/admin/instances/${remoteDomain}`, + }); + } + } + + return items; +} + +const redesignMessages = defineMessages({ + share: { id: 'account.menu.share', defaultMessage: 'Share…' }, + copy: { id: 'account.menu.copy', defaultMessage: 'Copy link' }, + copied: { + id: 'account.menu.copied', + defaultMessage: 'Copied account link to clipboard', + }, + mention: { id: 'account.menu.mention', defaultMessage: 'Mention' }, + direct: { + id: 'account.menu.direct', + defaultMessage: 'Privately mention', + }, + mute: { id: 'account.menu.mute', defaultMessage: 'Mute account' }, + unmute: { + id: 'account.menu.unmute', + defaultMessage: 'Unmute account', + }, + block: { id: 'account.menu.block', defaultMessage: 'Block account' }, + unblock: { + id: 'account.menu.unblock', + defaultMessage: 'Unblock account', + }, + domainBlock: { + id: 'account.menu.block_domain', + defaultMessage: 'Block {domain}', + }, + domainUnblock: { + id: 'account.menu.unblock_domain', + defaultMessage: 'Unblock {domain}', + }, + report: { id: 'account.menu.report', defaultMessage: 'Report account' }, + hideReblogs: { + id: 'account.menu.hide_reblogs', + defaultMessage: 'Hide boosts in timeline', + }, + showReblogs: { + id: 'account.menu.show_reblogs', + defaultMessage: 'Show boosts in timeline', + }, + addToList: { + id: 'account.menu.add_to_list', + defaultMessage: 'Add to list…', + }, + openOriginalPage: { + id: 'account.menu.open_original_page', + defaultMessage: 'View on {domain}', + }, + removeFollower: { + id: 'account.menu.remove_follower', + defaultMessage: 'Remove follower', + }, +}); + +function redesignMenuItems({ + account, + signedIn, + permissions, + intl, + relationship, + dispatch, +}: MenuItemsParams): MenuItem[] { + const items: MenuItem[] = []; + const isRemote = account.acct !== account.username; + const remoteDomain = isRemote ? account.acct.split('@')[1] : null; + + // Share and copy link options + if (account.url) { + if ('share' in navigator) { + items.push({ + text: intl.formatMessage(redesignMessages.share), + action: () => { + void navigator.share({ + url: account.url, + }); + }, + icon: ShareIcon, + }); + } + items.push( + { + text: intl.formatMessage(redesignMessages.copy), + action: () => { + void navigator.clipboard.writeText(account.url); + dispatch(showAlert({ message: redesignMessages.copied })); + }, + icon: LinkIcon, + }, + null, + ); + } + + // Mention and direct message options + if (signedIn && !account.suspended) { + items.push( + { + text: intl.formatMessage(redesignMessages.mention), + action: () => { + dispatch(mentionCompose(account)); + }, + }, + + { + text: intl.formatMessage(redesignMessages.direct), + action: () => { + dispatch(directCompose(account)); + }, + }, + null, + { text: intl.formatMessage( relationship?.note ? messages.editNote : messages.addNote, ), @@ -217,27 +560,34 @@ export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => { }), ); }, - }); - if (!relationship?.following) { - arr.push(null); - } - } + icon: EditIcon, + }, + null, + ); + } - if (relationship?.following) { - arr.push({ - text: intl.formatMessage( - relationship.endorsed ? messages.unendorse : messages.endorse, - ), - action: () => { - if (relationship.endorsed) { - dispatch(unpinAccount(account.id)); - } else { - dispatch(pinAccount(account.id)); - } - }, - }); - arr.push({ - text: intl.formatMessage(messages.add_or_remove_from_list), + // Open on remote page. + if (isRemote) { + items.push( + { + text: intl.formatMessage(redesignMessages.openOriginalPage, { + domain: remoteDomain, + }), + href: account.url, + }, + null, + ); + } + + if (!signedIn) { + return items; + } + + // List and featuring options + if (relationship?.following) { + items.push( + { + text: intl.formatMessage(redesignMessages.addToList), action: () => { dispatch( openModal({ @@ -248,12 +598,76 @@ export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => { }), ); }, - }); - arr.push(null); + }, + { + text: intl.formatMessage( + relationship.endorsed ? messages.unendorse : messages.endorse, + ), + action: () => { + if (relationship.endorsed) { + dispatch(unpinAccount(account.id)); + } else { + dispatch(pinAccount(account.id)); + } + }, + }, + null, + ); + + // Timeline options + if (!relationship.muting) { + items.push( + { + text: intl.formatMessage( + relationship.showing_reblogs + ? redesignMessages.hideReblogs + : redesignMessages.showReblogs, + ), + action: () => { + dispatch( + followAccount(account.id, { + reblogs: !relationship.showing_reblogs, + }), + ); + }, + }, + { + text: intl.formatMessage(messages.languages), + action: () => { + dispatch( + openModal({ + modalType: 'SUBSCRIBED_LANGUAGES', + modalProps: { + accountId: account.id, + }, + }), + ); + }, + }, + ); } - if (relationship?.followed_by) { - const handleRemoveFromFollowers = () => { + items.push( + { + text: intl.formatMessage( + relationship.muting ? redesignMessages.unmute : redesignMessages.mute, + ), + action: () => { + if (relationship.muting) { + dispatch(unmuteAccount(account.id)); + } else { + dispatch(initMuteModal(account)); + } + }, + }, + null, + ); + } + + if (relationship?.followed_by) { + items.push({ + text: intl.formatMessage(redesignMessages.removeFollower), + action: () => { dispatch( openModal({ modalType: 'CONFIRM', @@ -276,134 +690,91 @@ export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => { }, }), ); - }; + }, + dangerous: true, + icon: PersonRemoveIcon, + }); + } - arr.push({ - text: intl.formatMessage(messages.removeFromFollowers, { - name: account.username, - }), - action: handleRemoveFromFollowers, - dangerous: true, - }); - } - - if (relationship?.muting) { - arr.push({ - text: intl.formatMessage(messages.unmute, { - name: account.username, - }), - action: () => { - dispatch(unmuteAccount(account.id)); - }, - }); - } else { - arr.push({ - text: intl.formatMessage(messages.mute, { - name: account.username, - }), - action: () => { - dispatch(initMuteModal(account)); - }, - dangerous: true, - }); - } - - if (relationship?.blocking) { - arr.push({ - text: intl.formatMessage(messages.unblock, { - name: account.username, - }), - action: () => { - dispatch(unblockAccount(account.id)); - }, - }); - } else { - arr.push({ - text: intl.formatMessage(messages.block, { - name: account.username, - }), - action: () => { - dispatch(blockAccount(account.id)); - }, - dangerous: true, - }); - } - - if (!account.suspended) { - arr.push({ - text: intl.formatMessage(messages.report, { - name: account.username, - }), - action: () => { - dispatch(initReport(account)); - }, - dangerous: true, - }); - } - - const remoteDomain = isRemote ? account.acct.split('@')[1] : null; - if (remoteDomain) { - arr.push(null); - - if (relationship?.domain_blocking) { - arr.push({ - text: intl.formatMessage(messages.unblockDomain, { - domain: remoteDomain, - }), - action: () => { - dispatch(unblockDomain(remoteDomain)); - }, - }); + items.push({ + text: intl.formatMessage( + relationship?.blocking + ? redesignMessages.unblock + : redesignMessages.block, + ), + action: () => { + if (relationship?.blocking) { + dispatch(unblockAccount(account.id)); } else { - arr.push({ - text: intl.formatMessage(messages.blockDomain, { - domain: remoteDomain, - }), - action: () => { - dispatch(initDomainBlockModal(account)); - }, - dangerous: true, - }); + dispatch(blockAccount(account.id)); } - } + }, + dangerous: true, + icon: BlockIcon, + }); + if (!account.suspended) { + items.push({ + text: intl.formatMessage(redesignMessages.report), + action: () => { + dispatch(initReport(account)); + }, + dangerous: true, + icon: ReportIcon, + }); + } + + if (remoteDomain) { + items.push({ + text: intl.formatMessage( + relationship?.domain_blocking + ? redesignMessages.domainUnblock + : redesignMessages.domainBlock, + { + domain: remoteDomain, + }, + ), + action: () => { + if (relationship?.domain_blocking) { + dispatch(unblockDomain(remoteDomain)); + } else { + dispatch(initDomainBlockModal(account)); + } + }, + dangerous: true, + icon: BlockIcon, + iconId: 'domain-block', + }); + } + + if ( + (permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS || + (isRemote && + (permissions & PERMISSION_MANAGE_FEDERATION) === + PERMISSION_MANAGE_FEDERATION) + ) { + items.push(null); + if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) { + items.push({ + text: intl.formatMessage(messages.admin_account, { + name: account.username, + }), + href: `/admin/accounts/${account.id}`, + }); + } if ( - (permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS || - (isRemote && - (permissions & PERMISSION_MANAGE_FEDERATION) === - PERMISSION_MANAGE_FEDERATION) + remoteDomain && + (permissions & PERMISSION_MANAGE_FEDERATION) === + PERMISSION_MANAGE_FEDERATION ) { - arr.push(null); - if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) { - arr.push({ - text: intl.formatMessage(messages.admin_account, { - name: account.username, - }), - href: `/admin/accounts/${account.id}`, - }); - } - if ( - isRemote && - (permissions & PERMISSION_MANAGE_FEDERATION) === - PERMISSION_MANAGE_FEDERATION - ) { - arr.push({ - text: intl.formatMessage(messages.admin_domain, { - domain: remoteDomain, - }), - href: `/admin/instances/${remoteDomain}`, - }); - } + items.push({ + text: intl.formatMessage(messages.admin_domain, { + domain: remoteDomain, + }), + href: `/admin/instances/${remoteDomain}`, + }); } + } - return arr; - }, [account, signedIn, permissions, intl, relationship, dispatch]); - return ( - - ); -}; + return items; +} diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/redesign.module.scss b/app/javascript/flavours/glitch/features/account_timeline/components/redesign.module.scss index 80195a7a82..b86446efa3 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/redesign.module.scss +++ b/app/javascript/flavours/glitch/features/account_timeline/components/redesign.module.scss @@ -1,3 +1,7 @@ +.barWrapper { + border-bottom: none; +} + .nameWrapper { display: flex; gap: 16px; @@ -45,6 +49,43 @@ } } +$button-breakpoint: 420px; +$button-fallback-breakpoint: #{$button-breakpoint} + 55px; + +.buttonsDesktop { + @container (width < #{$button-breakpoint}) { + display: none; + } + + @supports (not (container-type: inline-size)) { + @media (max-width: #{$button-fallback-breakpoint}) { + display: none; + } + } +} + +.buttonsMobile { + position: sticky; + bottom: 55px; // Height of bottom nav bar. + + @container (width >= #{$button-breakpoint}) { + display: none; + } + + @supports (not (container-type: inline-size)) { + @media (min-width: (#{$button-fallback-breakpoint} + 1px)) { + display: none; + } + } +} + +.buttonsMobileIsStuck { + padding: 12px 16px; + background-color: var(--color-bg-primary); + border-top: 1px solid var(--color-border-primary); + margin: 0 -20px; +} + .badge { background-color: var(--color-bg-secondary); border: none; diff --git a/app/javascript/flavours/glitch/models/dropdown_menu.ts b/app/javascript/flavours/glitch/models/dropdown_menu.ts index 01da286936..a5decf607b 100644 --- a/app/javascript/flavours/glitch/models/dropdown_menu.ts +++ b/app/javascript/flavours/glitch/models/dropdown_menu.ts @@ -6,6 +6,7 @@ interface BaseMenuItem { text: string; description?: string; icon?: IconProp; + iconId?: string; highlighted?: boolean; disabled?: boolean; dangerous?: boolean; diff --git a/app/javascript/flavours/glitch/styles/mastodon/components.scss b/app/javascript/flavours/glitch/styles/mastodon/components.scss index c0f986e9cb..ac501195b7 100644 --- a/app/javascript/flavours/glitch/styles/mastodon/components.scss +++ b/app/javascript/flavours/glitch/styles/mastodon/components.scss @@ -8582,7 +8582,6 @@ noscript { } .account__header { - overflow: hidden; container: account-header / inline-size; &.inactive {