diff --git a/app/javascript/mastodon/actions/search.js b/app/javascript/mastodon/actions/search.js index 56608f28b..605a457a2 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 72eb7e6b6..ff8dc5fa9 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) && (