~cytrogen/masto-fe

8099ba04bee735b5136ac9b3928283e055a0458d — Eugen Rochko 2 years ago e9a79d4
Change hashtags and mentions in bios to open in-app in web UI (#24643)

M app/javascript/mastodon/actions/search.js => app/javascript/mastodon/actions/search.js +9 -4
@@ 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();
    }
  });
};


M app/javascript/mastodon/features/account/components/header.jsx => app/javascript/mastodon/features/account/components/header.jsx +62 -1
@@ 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) && (
            <div className='account__header__extra'>
              <div className='account__header__bio'>
              <div className='account__header__bio' ref={this.setRef}>
                {(account.get('id') !== me && signedIn) && <AccountNoteContainer account={account} />}

                {account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content translate' dangerouslySetInnerHTML={content} />}

M app/javascript/mastodon/features/account_timeline/components/header.jsx => app/javascript/mastodon/features/account_timeline/components/header.jsx +2 -0
@@ 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}
        />

M app/javascript/mastodon/features/account_timeline/containers/header_container.jsx => app/javascript/mastodon/features/account_timeline/containers/header_container.jsx +5 -0
@@ 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));

M app/javascript/mastodon/features/compose/components/search.jsx => app/javascript/mastodon/features/compose/components/search.jsx +2 -2
@@ 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 = () => {

M app/javascript/mastodon/features/compose/containers/search_container.js => app/javascript/mastodon/features/compose/containers/search_container.js +2 -2
@@ 34,8 34,8 @@ const mapDispatchToProps = dispatch => ({
    dispatch(showSearch());
  },

  onOpenURL (routerHistory) {
    dispatch(openURL(routerHistory));
  onOpenURL (q, routerHistory) {
    dispatch(openURL(q, routerHistory));
  },

  onClickSearchResult (q, type) {