From 49493f8b50497d029f83ba4ad73fabd7d91dab85 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 25 Apr 2023 06:33:21 +0200 Subject: [PATCH] Change hashtags and mentions in bios to open in-app in web UI (#24643) --- app/javascript/mastodon/actions/search.js | 13 ++-- .../features/account/components/header.jsx | 63 ++++++++++++++++++- .../account_timeline/components/header.jsx | 2 + .../containers/header_container.jsx | 5 ++ .../features/compose/components/search.jsx | 4 +- .../compose/containers/search_container.js | 4 +- 6 files changed, 82 insertions(+), 9 deletions(-) diff --git a/app/javascript/mastodon/actions/search.js b/app/javascript/mastodon/actions/search.js index 56608f28ba..605a457a2e 100644 --- a/app/javascript/mastodon/actions/search.js +++ b/app/javascript/mastodon/actions/search.js @@ -135,8 +135,7 @@ export const showSearch = () => ({ type: SEARCH_SHOW, }); -export const openURL = routerHistory => (dispatch, getState) => { - const value = getState().getIn(['search', 'value']); +export const openURL = (value, history, onFailure) => (dispatch, getState) => { const signedIn = !!getState().getIn(['meta', 'me']); if (!signedIn) { @@ -148,15 +147,21 @@ export const openURL = routerHistory => (dispatch, getState) => { api(getState).get('/api/v2/search', { params: { q: value, resolve: true } }).then(response => { if (response.data.accounts?.length > 0) { dispatch(importFetchedAccounts(response.data.accounts)); - routerHistory.push(`/@${response.data.accounts[0].acct}`); + history.push(`/@${response.data.accounts[0].acct}`); } else if (response.data.statuses?.length > 0) { dispatch(importFetchedStatuses(response.data.statuses)); - routerHistory.push(`/@${response.data.statuses[0].account.acct}/${response.data.statuses[0].id}`); + history.push(`/@${response.data.statuses[0].account.acct}/${response.data.statuses[0].id}`); + } else if (onFailure) { + onFailure(); } dispatch(fetchSearchSuccess(response.data, value)); }).catch(err => { dispatch(fetchSearchFail(err)); + + if (onFailure) { + onFailure(); + } }); }; diff --git a/app/javascript/mastodon/features/account/components/header.jsx b/app/javascript/mastodon/features/account/components/header.jsx index 72eb7e6b65..ff8dc5fa9f 100644 --- a/app/javascript/mastodon/features/account/components/header.jsx +++ b/app/javascript/mastodon/features/account/components/header.jsx @@ -80,6 +80,7 @@ class Header extends ImmutablePureComponent { static contextTypes = { identity: PropTypes.object, + router: PropTypes.object, }; static propTypes = { @@ -101,11 +102,16 @@ class Header extends ImmutablePureComponent { onChangeLanguages: PropTypes.func.isRequired, onInteractionModal: PropTypes.func.isRequired, onOpenAvatar: PropTypes.func.isRequired, + onOpenURL: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, domain: PropTypes.string.isRequired, hidden: PropTypes.bool, }; + setRef = c => { + this.node = c; + }; + openEditProfile = () => { window.open('/settings/profile', '_blank'); }; @@ -162,6 +168,61 @@ class Header extends ImmutablePureComponent { }); }; + handleHashtagClick = e => { + const { router } = this.context; + const value = e.currentTarget.textContent.replace(/^#/, ''); + + if (router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { + e.preventDefault(); + router.history.push(`/tags/${value}`); + } + }; + + handleMentionClick = e => { + const { router } = this.context; + const { onOpenURL } = this.props; + + if (router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { + e.preventDefault(); + + const link = e.currentTarget; + + onOpenURL(link.href, router.history, () => { + window.location = link.href; + }); + } + }; + + _attachLinkEvents () { + const node = this.node; + + if (!node) { + return; + } + + const links = node.querySelectorAll('a'); + + let link; + + for (var i = 0; i < links.length; ++i) { + link = links[i]; + + if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) { + link.addEventListener('click', this.handleHashtagClick, false); + } else if (link.classList.contains('mention')) { + link.addEventListener('click', this.handleMentionClick, false); + } + } + } + + componentDidMount () { + this._attachLinkEvents(); + } + + componentDidUpdate () { + this._attachLinkEvents(); + } + render () { const { account, hidden, intl, domain } = this.props; const { signedIn, permissions } = this.context.identity; @@ -360,7 +421,7 @@ class Header extends ImmutablePureComponent { {!(suspended || hidden) && (
-
+
{(account.get('id') !== me && signedIn) && } {account.get('note').length > 0 && account.get('note') !== '

' &&
} diff --git a/app/javascript/mastodon/features/account_timeline/components/header.jsx b/app/javascript/mastodon/features/account_timeline/components/header.jsx index c008f0342d..e64deaefa6 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.jsx +++ b/app/javascript/mastodon/features/account_timeline/components/header.jsx @@ -26,6 +26,7 @@ export default class Header extends ImmutablePureComponent { onChangeLanguages: PropTypes.func.isRequired, onInteractionModal: PropTypes.func.isRequired, onOpenAvatar: PropTypes.func.isRequired, + onOpenURL: PropTypes.func.isRequired, hideTabs: PropTypes.bool, domain: PropTypes.string.isRequired, hidden: PropTypes.bool, @@ -137,6 +138,7 @@ export default class Header extends ImmutablePureComponent { onChangeLanguages={this.handleChangeLanguages} onInteractionModal={this.handleInteractionModal} onOpenAvatar={this.handleOpenAvatar} + onOpenURL={this.props.onOpenURL} domain={this.props.domain} hidden={hidden} /> diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx b/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx index f53cd24807..419a9fa566 100644 --- a/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx +++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.jsx @@ -10,6 +10,7 @@ import { pinAccount, unpinAccount, } from '../../../actions/accounts'; +import { openURL } from 'mastodon/actions/search'; import { mentionCompose, directCompose, @@ -159,6 +160,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ })); }, + onOpenURL (url, routerHistory, onFailure) { + dispatch(openURL(url, routerHistory, onFailure)); + }, + }); export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header)); diff --git a/app/javascript/mastodon/features/compose/components/search.jsx b/app/javascript/mastodon/features/compose/components/search.jsx index 46723f5cce..7fff587d68 100644 --- a/app/javascript/mastodon/features/compose/components/search.jsx +++ b/app/javascript/mastodon/features/compose/components/search.jsx @@ -161,9 +161,9 @@ class Search extends React.PureComponent { handleURLClick = () => { const { router } = this.context; - const { onOpenURL } = this.props; + const { value, onOpenURL } = this.props; - onOpenURL(router.history); + onOpenURL(value, router.history); }; handleStatusSearch = () => { diff --git a/app/javascript/mastodon/features/compose/containers/search_container.js b/app/javascript/mastodon/features/compose/containers/search_container.js index 3ee55fae59..3d2d728c8d 100644 --- a/app/javascript/mastodon/features/compose/containers/search_container.js +++ b/app/javascript/mastodon/features/compose/containers/search_container.js @@ -34,8 +34,8 @@ const mapDispatchToProps = dispatch => ({ dispatch(showSearch()); }, - onOpenURL (routerHistory) { - dispatch(openURL(routerHistory)); + onOpenURL (q, routerHistory) { + dispatch(openURL(q, routerHistory)); }, onClickSearchResult (q, type) {