summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2022-11-10 08:49:35 +0100
committerGitHub <noreply@github.com>2022-11-10 08:49:35 +0100
commitef582dc4f2fe53b08988faf8d51f567ac32e5b93 (patch)
tree0f666648e81c69489cda6482bff484551beed34e
parente37e8deb0ff207d36bb359ce395e2888dacc216d (diff)
Add option to open original page in dropdowns of remote content in web UI (#20299)
Change profile picture click to open profile picture in modal in web UI
-rw-r--r--app/javascript/mastodon/components/status_action_bar.js25
-rw-r--r--app/javascript/mastodon/features/account/components/header.js28
-rw-r--r--app/javascript/mastodon/features/account_timeline/components/header.js6
-rw-r--r--app/javascript/mastodon/features/account_timeline/containers/header_container.js7
-rw-r--r--app/javascript/mastodon/features/status/components/action_bar.js24
-rw-r--r--app/javascript/mastodon/features/ui/components/image_modal.js59
-rw-r--r--app/javascript/mastodon/features/ui/components/modal_root.js2
7 files changed, 111 insertions, 40 deletions
diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js
index b07837dcaf3..2a1fedb93bf 100644
--- a/app/javascript/mastodon/components/status_action_bar.js
+++ b/app/javascript/mastodon/components/status_action_bar.js
@@ -45,6 +45,7 @@ const messages = defineMessages({
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
filter: { id: 'status.filter', defaultMessage: 'Filter this post' },
+ openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
});
const mapStateToProps = (state, { status }) => ({
@@ -221,25 +222,10 @@ class StatusActionBar extends ImmutablePureComponent {
}
handleCopy = () => {
- const url = this.props.status.get('url');
- const textarea = document.createElement('textarea');
-
- textarea.textContent = url;
- textarea.style.position = 'fixed';
-
- document.body.appendChild(textarea);
-
- try {
- textarea.select();
- document.execCommand('copy');
- } catch (e) {
-
- } finally {
- document.body.removeChild(textarea);
- }
+ const url = this.props.status.get('url');
+ navigator.clipboard.writeText(url);
}
-
handleHideClick = () => {
this.props.onFilter();
}
@@ -254,12 +240,17 @@ class StatusActionBar extends ImmutablePureComponent {
const mutingConversation = status.get('muted');
const account = status.get('account');
const writtenByMe = status.getIn(['account', 'id']) === me;
+ const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']);
let menu = [];
menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
if (publicStatus) {
+ if (isRemote) {
+ menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') });
+ }
+
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
}
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index 8d3b3c5e64b..c38eea55b34 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/app/javascript/mastodon/features/account/components/header.js
@@ -53,6 +53,7 @@ const messages = defineMessages({
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
languages: { id: 'account.languages', defaultMessage: 'Change subscribed languages' },
+ openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
});
const titleFromAccount = account => {
@@ -97,6 +98,7 @@ class Header extends ImmutablePureComponent {
onEditAccountNote: PropTypes.func.isRequired,
onChangeLanguages: PropTypes.func.isRequired,
onInteractionModal: PropTypes.func.isRequired,
+ onOpenAvatar: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
domain: PropTypes.string.isRequired,
hidden: PropTypes.bool,
@@ -140,6 +142,13 @@ class Header extends ImmutablePureComponent {
}
}
+ handleAvatarClick = e => {
+ if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+ e.preventDefault();
+ this.props.onOpenAvatar();
+ }
+ }
+
render () {
const { account, hidden, intl, domain } = this.props;
const { signedIn } = this.context.identity;
@@ -148,7 +157,9 @@ class Header extends ImmutablePureComponent {
return null;
}
- const suspended = account.get('suspended');
+ const suspended = account.get('suspended');
+ const isRemote = account.get('acct') !== account.get('username');
+ const remoteDomain = isRemote ? account.get('acct').split('@')[1] : null;
let info = [];
let actionBtn = '';
@@ -200,6 +211,11 @@ class Header extends ImmutablePureComponent {
menu.push(null);
}
+ if (isRemote) {
+ menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: account.get('url') });
+ menu.push(null);
+ }
+
if ('share' in navigator) {
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
menu.push(null);
@@ -250,15 +266,13 @@ class Header extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport });
}
- if (signedIn && account.get('acct') !== account.get('username')) {
- const domain = account.get('acct').split('@')[1];
-
+ if (signedIn && isRemote) {
menu.push(null);
if (account.getIn(['relationship', 'domain_blocking'])) {
- menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.props.onUnblockDomain });
+ menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain: remoteDomain }), action: this.props.onUnblockDomain });
} else {
- menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.props.onBlockDomain });
+ menu.push({ text: intl.formatMessage(messages.blockDomain, { domain: remoteDomain }), action: this.props.onBlockDomain });
}
}
@@ -296,7 +310,7 @@ class Header extends ImmutablePureComponent {
<div className='account__header__bar'>
<div className='account__header__tabs'>
- <a className='avatar' href={account.get('url')} rel='noopener noreferrer' target='_blank'>
+ <a className='avatar' href={account.get('avatar')} rel='noopener noreferrer' target='_blank' onClick={this.handleAvatarClick}>
<Avatar account={suspended || hidden ? undefined : account} size={90} />
</a>
diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js
index f31848f412d..d67e307ffca 100644
--- a/app/javascript/mastodon/features/account_timeline/components/header.js
+++ b/app/javascript/mastodon/features/account_timeline/components/header.js
@@ -24,6 +24,7 @@ export default class Header extends ImmutablePureComponent {
onAddToList: PropTypes.func.isRequired,
onChangeLanguages: PropTypes.func.isRequired,
onInteractionModal: PropTypes.func.isRequired,
+ onOpenAvatar: PropTypes.func.isRequired,
hideTabs: PropTypes.bool,
domain: PropTypes.string.isRequired,
hidden: PropTypes.bool,
@@ -101,6 +102,10 @@ export default class Header extends ImmutablePureComponent {
this.props.onInteractionModal(this.props.account);
}
+ handleOpenAvatar = () => {
+ this.props.onOpenAvatar(this.props.account);
+ }
+
render () {
const { account, hidden, hideTabs } = this.props;
@@ -129,6 +134,7 @@ export default class Header extends ImmutablePureComponent {
onEditAccountNote={this.handleEditAccountNote}
onChangeLanguages={this.handleChangeLanguages}
onInteractionModal={this.handleInteractionModal}
+ onOpenAvatar={this.handleOpenAvatar}
domain={this.props.domain}
hidden={hidden}
/>
diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.js b/app/javascript/mastodon/features/account_timeline/containers/header_container.js
index 1d09cd1efc0..f53cd248079 100644
--- a/app/javascript/mastodon/features/account_timeline/containers/header_container.js
+++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.js
@@ -152,6 +152,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}));
},
+ onOpenAvatar (account) {
+ dispatch(openModal('IMAGE', {
+ src: account.get('avatar'),
+ alt: account.get('acct'),
+ }));
+ },
+
});
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));
diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js
index fc82aa9c2ec..c1242754cc9 100644
--- a/app/javascript/mastodon/features/status/components/action_bar.js
+++ b/app/javascript/mastodon/features/status/components/action_bar.js
@@ -39,6 +39,7 @@ const messages = defineMessages({
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
+ openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
});
const mapStateToProps = (state, { status }) => ({
@@ -174,22 +175,8 @@ class ActionBar extends React.PureComponent {
}
handleCopy = () => {
- const url = this.props.status.get('url');
- const textarea = document.createElement('textarea');
-
- textarea.textContent = url;
- textarea.style.position = 'fixed';
-
- document.body.appendChild(textarea);
-
- try {
- textarea.select();
- document.execCommand('copy');
- } catch (e) {
-
- } finally {
- document.body.removeChild(textarea);
- }
+ const url = this.props.status.get('url');
+ navigator.clipboard.writeText(url);
}
render () {
@@ -201,10 +188,15 @@ class ActionBar extends React.PureComponent {
const mutingConversation = status.get('muted');
const account = status.get('account');
const writtenByMe = status.getIn(['account', 'id']) === me;
+ const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']);
let menu = [];
if (publicStatus) {
+ if (isRemote) {
+ menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') });
+ }
+
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
menu.push(null);
diff --git a/app/javascript/mastodon/features/ui/components/image_modal.js b/app/javascript/mastodon/features/ui/components/image_modal.js
new file mode 100644
index 00000000000..7522c3da553
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/image_modal.js
@@ -0,0 +1,59 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import { defineMessages, injectIntl } from 'react-intl';
+import IconButton from 'mastodon/components/icon_button';
+import ImageLoader from './image_loader';
+
+const messages = defineMessages({
+ close: { id: 'lightbox.close', defaultMessage: 'Close' },
+});
+
+export default @injectIntl
+class ImageModal extends React.PureComponent {
+
+ static propTypes = {
+ src: PropTypes.string.isRequired,
+ alt: PropTypes.string.isRequired,
+ onClose: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ };
+
+ state = {
+ navigationHidden: false,
+ };
+
+ toggleNavigation = () => {
+ this.setState(prevState => ({
+ navigationHidden: !prevState.navigationHidden,
+ }));
+ };
+
+ render () {
+ const { intl, src, alt, onClose } = this.props;
+ const { navigationHidden } = this.state;
+
+ const navigationClassName = classNames('media-modal__navigation', {
+ 'media-modal__navigation--hidden': navigationHidden,
+ });
+
+ return (
+ <div className='modal-root__modal media-modal'>
+ <div className='media-modal__closer' role='presentation' onClick={onClose} >
+ <ImageLoader
+ src={src}
+ width={400}
+ height={400}
+ alt={alt}
+ onClick={this.toggleNavigation}
+ />
+ </div>
+
+ <div className={navigationClassName}>
+ <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={40} />
+ </div>
+ </div>
+ );
+ }
+
+}
diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js
index 484ebbd8b63..6c4aabae5c3 100644
--- a/app/javascript/mastodon/features/ui/components/modal_root.js
+++ b/app/javascript/mastodon/features/ui/components/modal_root.js
@@ -12,6 +12,7 @@ import BoostModal from './boost_modal';
import AudioModal from './audio_modal';
import ConfirmationModal from './confirmation_modal';
import FocalPointModal from './focal_point_modal';
+import ImageModal from './image_modal';
import {
MuteModal,
BlockModal,
@@ -31,6 +32,7 @@ const MODAL_COMPONENTS = {
'MEDIA': () => Promise.resolve({ default: MediaModal }),
'VIDEO': () => Promise.resolve({ default: VideoModal }),
'AUDIO': () => Promise.resolve({ default: AudioModal }),
+ 'IMAGE': () => Promise.resolve({ default: ImageModal }),
'BOOST': () => Promise.resolve({ default: BoostModal }),
'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
'MUTE': MuteModal,