Add prominent share/copy button on profiles in web UI (#27865)
parent
7232d4750d
commit
87696ea26e
|
@ -0,0 +1,44 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useState, useCallback } from 'react';
|
||||||
|
|
||||||
|
import { defineMessages } from 'react-intl';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
|
import { ReactComponent as ContentCopyIcon } from '@material-symbols/svg-600/outlined/content_copy.svg';
|
||||||
|
|
||||||
|
import { showAlert } from 'mastodon/actions/alerts';
|
||||||
|
import { IconButton } from 'mastodon/components/icon_button';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
copied: { id: 'copy_icon_button.copied', defaultMessage: 'Copied to clipboard' },
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CopyIconButton = ({ title, value, className }) => {
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
navigator.clipboard.writeText(value);
|
||||||
|
setCopied(true);
|
||||||
|
dispatch(showAlert({ message: messages.copied }));
|
||||||
|
setTimeout(() => setCopied(false), 700);
|
||||||
|
}, [setCopied, value, dispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
className={classNames(className, copied ? 'copied' : 'copyable')}
|
||||||
|
title={title}
|
||||||
|
onClick={handleClick}
|
||||||
|
iconComponent={ContentCopyIcon}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
CopyIconButton.propTypes = {
|
||||||
|
title: PropTypes.string,
|
||||||
|
value: PropTypes.string,
|
||||||
|
className: PropTypes.string,
|
||||||
|
};
|
|
@ -14,10 +14,12 @@ import { ReactComponent as LockIcon } from '@material-symbols/svg-600/outlined/l
|
||||||
import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg';
|
import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg';
|
||||||
import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications.svg';
|
import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications.svg';
|
||||||
import { ReactComponent as NotificationsActiveIcon } from '@material-symbols/svg-600/outlined/notifications_active-fill.svg';
|
import { ReactComponent as NotificationsActiveIcon } from '@material-symbols/svg-600/outlined/notifications_active-fill.svg';
|
||||||
|
import { ReactComponent as ShareIcon } from '@material-symbols/svg-600/outlined/share.svg';
|
||||||
|
|
||||||
import { Avatar } from 'mastodon/components/avatar';
|
import { Avatar } from 'mastodon/components/avatar';
|
||||||
import { Badge, AutomatedBadge, GroupBadge } from 'mastodon/components/badge';
|
import { Badge, AutomatedBadge, GroupBadge } from 'mastodon/components/badge';
|
||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
|
import { CopyIconButton } from 'mastodon/components/copy_icon_button';
|
||||||
import { FollowersCounter, FollowingCounter, StatusesCounter } from 'mastodon/components/counters';
|
import { FollowersCounter, FollowingCounter, StatusesCounter } from 'mastodon/components/counters';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
import { IconButton } from 'mastodon/components/icon_button';
|
import { IconButton } from 'mastodon/components/icon_button';
|
||||||
|
@ -46,6 +48,7 @@ const messages = defineMessages({
|
||||||
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
||||||
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
|
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
|
||||||
share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
|
share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
|
||||||
|
copy: { id: 'account.copy', defaultMessage: 'Copy link to profile' },
|
||||||
media: { id: 'account.media', defaultMessage: 'Media' },
|
media: { id: 'account.media', defaultMessage: 'Media' },
|
||||||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
|
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
|
||||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
|
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
|
||||||
|
@ -245,10 +248,9 @@ class Header extends ImmutablePureComponent {
|
||||||
const isRemote = account.get('acct') !== account.get('username');
|
const isRemote = account.get('acct') !== account.get('username');
|
||||||
const remoteDomain = isRemote ? account.get('acct').split('@')[1] : null;
|
const remoteDomain = isRemote ? account.get('acct').split('@')[1] : null;
|
||||||
|
|
||||||
|
let actionBtn, bellBtn, lockedIcon, shareBtn;
|
||||||
|
|
||||||
let info = [];
|
let info = [];
|
||||||
let actionBtn = '';
|
|
||||||
let bellBtn = '';
|
|
||||||
let lockedIcon = '';
|
|
||||||
let menu = [];
|
let menu = [];
|
||||||
|
|
||||||
if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
|
if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
|
||||||
|
@ -267,6 +269,12 @@ class Header extends ImmutablePureComponent {
|
||||||
bellBtn = <IconButton icon={account.getIn(['relationship', 'notifying']) ? 'bell' : 'bell-o'} iconComponent={account.getIn(['relationship', 'notifying']) ? NotificationsActiveIcon : NotificationsIcon} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />;
|
bellBtn = <IconButton icon={account.getIn(['relationship', 'notifying']) ? 'bell' : 'bell-o'} iconComponent={account.getIn(['relationship', 'notifying']) ? NotificationsActiveIcon : NotificationsIcon} active={account.getIn(['relationship', 'notifying'])} title={intl.formatMessage(account.getIn(['relationship', 'notifying']) ? messages.disableNotifications : messages.enableNotifications, { name: account.get('username') })} onClick={this.props.onNotifyToggle} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('share' in navigator) {
|
||||||
|
shareBtn = <IconButton className='optional' iconComponent={ShareIcon} title={intl.formatMessage(messages.share, { name: account.get('username') })} onClick={this.handleShare} />;
|
||||||
|
} else {
|
||||||
|
shareBtn = <CopyIconButton className='optional' title={intl.formatMessage(messages.copy)} value={account.get('url')} />;
|
||||||
|
}
|
||||||
|
|
||||||
if (me !== account.get('id')) {
|
if (me !== account.get('id')) {
|
||||||
if (signedIn && !account.get('relationship')) { // Wait until the relationship is loaded
|
if (signedIn && !account.get('relationship')) { // Wait until the relationship is loaded
|
||||||
actionBtn = '';
|
actionBtn = '';
|
||||||
|
@ -297,10 +305,6 @@ class Header extends ImmutablePureComponent {
|
||||||
|
|
||||||
if (isRemote) {
|
if (isRemote) {
|
||||||
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: account.get('url') });
|
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: account.get('url') });
|
||||||
}
|
|
||||||
|
|
||||||
if ('share' in navigator && !account.get('suspended')) {
|
|
||||||
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
|
|
||||||
menu.push(null);
|
menu.push(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,6 +418,7 @@ class Header extends ImmutablePureComponent {
|
||||||
<>
|
<>
|
||||||
{actionBtn}
|
{actionBtn}
|
||||||
{bellBtn}
|
{bellBtn}
|
||||||
|
{shareBtn}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
"account.blocked": "Blocked",
|
"account.blocked": "Blocked",
|
||||||
"account.browse_more_on_origin_server": "Browse more on the original profile",
|
"account.browse_more_on_origin_server": "Browse more on the original profile",
|
||||||
"account.cancel_follow_request": "Cancel follow",
|
"account.cancel_follow_request": "Cancel follow",
|
||||||
|
"account.copy": "Copy link to profile",
|
||||||
"account.direct": "Privately mention @{name}",
|
"account.direct": "Privately mention @{name}",
|
||||||
"account.disable_notifications": "Stop notifying me when @{name} posts",
|
"account.disable_notifications": "Stop notifying me when @{name} posts",
|
||||||
"account.domain_blocked": "Domain blocked",
|
"account.domain_blocked": "Domain blocked",
|
||||||
|
@ -191,6 +192,7 @@
|
||||||
"conversation.mark_as_read": "Mark as read",
|
"conversation.mark_as_read": "Mark as read",
|
||||||
"conversation.open": "View conversation",
|
"conversation.open": "View conversation",
|
||||||
"conversation.with": "With {names}",
|
"conversation.with": "With {names}",
|
||||||
|
"copy_icon_button.copied": "Copied to clipboard",
|
||||||
"copypaste.copied": "Copied",
|
"copypaste.copied": "Copied",
|
||||||
"copypaste.copy_to_clipboard": "Copy to clipboard",
|
"copypaste.copy_to_clipboard": "Copy to clipboard",
|
||||||
"directory.federated": "From known fediverse",
|
"directory.federated": "From known fediverse",
|
||||||
|
|
|
@ -286,6 +286,17 @@
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.copyable {
|
||||||
|
transition: all 300ms linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.copied {
|
||||||
|
border-color: $valid-value-color;
|
||||||
|
color: $valid-value-color;
|
||||||
|
transition: none;
|
||||||
|
background-color: rgba($valid-value-color, 0.15);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-icon-button {
|
.text-icon-button {
|
||||||
|
@ -7373,6 +7384,16 @@ noscript {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.copied {
|
||||||
|
border-color: $valid-value-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (width <= 427px) {
|
||||||
|
.optional {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue