Fix #100 - Add "back" button to certain views
Also fix reloading of timelines after merge-type events
This commit is contained in:
		
							parent
							
								
									8698cd3281
								
							
						
					
					
						commit
						04bbc57690
					
				
					 10 changed files with 80 additions and 31 deletions
				
			
		| 
						 | 
				
			
			@ -53,7 +53,7 @@ export function fetchAccount(id) {
 | 
			
		|||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function fetchAccountTimeline(id) {
 | 
			
		||||
export function fetchAccountTimeline(id, replace = false) {
 | 
			
		||||
  return (dispatch, getState) => {
 | 
			
		||||
    dispatch(fetchAccountTimelineRequest(id));
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -62,12 +62,12 @@ export function fetchAccountTimeline(id) {
 | 
			
		|||
 | 
			
		||||
    let params = '';
 | 
			
		||||
 | 
			
		||||
    if (newestId !== null) {
 | 
			
		||||
    if (newestId !== null && !replace) {
 | 
			
		||||
      params = `?since_id=${newestId}`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    api(getState).get(`/api/v1/accounts/${id}/statuses${params}`).then(response => {
 | 
			
		||||
      dispatch(fetchAccountTimelineSuccess(id, response.data));
 | 
			
		||||
      dispatch(fetchAccountTimelineSuccess(id, response.data, replace));
 | 
			
		||||
    }).catch(error => {
 | 
			
		||||
      dispatch(fetchAccountTimelineFail(id, error));
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			@ -184,11 +184,12 @@ export function fetchAccountTimelineRequest(id) {
 | 
			
		|||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function fetchAccountTimelineSuccess(id, statuses) {
 | 
			
		||||
export function fetchAccountTimelineSuccess(id, statuses, replace) {
 | 
			
		||||
  return {
 | 
			
		||||
    type: ACCOUNT_TIMELINE_FETCH_SUCCESS,
 | 
			
		||||
    id: id,
 | 
			
		||||
    statuses: statuses
 | 
			
		||||
    statuses: statuses,
 | 
			
		||||
    replace: replace
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,11 +11,12 @@ export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST';
 | 
			
		|||
export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS';
 | 
			
		||||
export const TIMELINE_EXPAND_FAIL    = 'TIMELINE_EXPAND_FAIL';
 | 
			
		||||
 | 
			
		||||
export function refreshTimelineSuccess(timeline, statuses) {
 | 
			
		||||
export function refreshTimelineSuccess(timeline, statuses, replace) {
 | 
			
		||||
  return {
 | 
			
		||||
    type: TIMELINE_REFRESH_SUCCESS,
 | 
			
		||||
    timeline: timeline,
 | 
			
		||||
    statuses: statuses
 | 
			
		||||
    statuses: statuses,
 | 
			
		||||
    replace: replace
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -41,7 +42,7 @@ export function refreshTimelineRequest(timeline) {
 | 
			
		|||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function refreshTimeline(timeline) {
 | 
			
		||||
export function refreshTimeline(timeline, replace = false) {
 | 
			
		||||
  return function (dispatch, getState) {
 | 
			
		||||
    dispatch(refreshTimelineRequest(timeline));
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -50,12 +51,12 @@ export function refreshTimeline(timeline) {
 | 
			
		|||
 | 
			
		||||
    let params = '';
 | 
			
		||||
 | 
			
		||||
    if (newestId !== null) {
 | 
			
		||||
    if (newestId !== null && !replace) {
 | 
			
		||||
      params = `?since_id=${newestId}`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    api(getState).get(`/api/v1/statuses/${timeline}${params}`).then(function (response) {
 | 
			
		||||
      dispatch(refreshTimelineSuccess(timeline, response.data));
 | 
			
		||||
      dispatch(refreshTimelineSuccess(timeline, response.data, replace));
 | 
			
		||||
    }).catch(function (error) {
 | 
			
		||||
      dispatch(refreshTimelineFail(timeline, error));
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,40 @@
 | 
			
		|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
 | 
			
		||||
 | 
			
		||||
const outerStyle = {
 | 
			
		||||
  padding: '15px',
 | 
			
		||||
  fontSize: '16px',
 | 
			
		||||
  background: '#2f3441',
 | 
			
		||||
  flex: '0 0 auto',
 | 
			
		||||
  cursor: 'pointer',
 | 
			
		||||
  color: '#2b90d9'
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const iconStyle = {
 | 
			
		||||
  display: 'inline-block',
 | 
			
		||||
  marginRight: '5px'
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ColumnBackButton = React.createClass({
 | 
			
		||||
 | 
			
		||||
  contextTypes: {
 | 
			
		||||
    router: React.PropTypes.object
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  mixins: [PureRenderMixin],
 | 
			
		||||
 | 
			
		||||
  handleClick () {
 | 
			
		||||
    this.context.router.goBack();
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    return (
 | 
			
		||||
      <div onClick={this.handleClick} style={outerStyle}>
 | 
			
		||||
        <i className='fa fa-fw fa-chevron-left' style={iconStyle} />
 | 
			
		||||
        Back
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default ColumnBackButton;
 | 
			
		||||
| 
						 | 
				
			
			@ -54,9 +54,9 @@ const Mastodon = React.createClass({
 | 
			
		|||
              return store.dispatch(deleteFromTimelines(data.id));
 | 
			
		||||
            case 'merge':
 | 
			
		||||
            case 'unmerge':
 | 
			
		||||
              return store.dispatch(refreshTimeline('home'));
 | 
			
		||||
              return store.dispatch(refreshTimeline('home', true));
 | 
			
		||||
            case 'block':
 | 
			
		||||
              return store.dispatch(refreshTimeline('mentions'));
 | 
			
		||||
              return store.dispatch(refreshTimeline('mentions', true));
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,16 +26,16 @@ const Header = React.createClass({
 | 
			
		|||
 | 
			
		||||
    return (
 | 
			
		||||
      <div style={{ flex: '0 0 auto', background: '#2f3441', textAlign: 'center', backgroundImage: `url(${account.get('header')})`, backgroundSize: 'cover', position: 'relative' }}>
 | 
			
		||||
        <div style={{ background: 'rgba(47, 52, 65, 0.8)', padding: '30px 10px' }}>
 | 
			
		||||
        <div style={{ background: 'rgba(47, 52, 65, 0.8)', padding: '20px 10px' }}>
 | 
			
		||||
          <a href={account.get('url')} target='_blank' rel='noopener' style={{ display: 'block', color: 'inherit', textDecoration: 'none' }}>
 | 
			
		||||
            <div style={{ width: '90px', margin: '0 auto', marginBottom: '15px' }}>
 | 
			
		||||
            <div style={{ width: '90px', margin: '0 auto', marginBottom: '10px' }}>
 | 
			
		||||
              <img src={account.get('avatar')} alt='' style={{ display: 'block', width: '90px', height: '90px', borderRadius: '90px' }} />
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <span style={{ display: 'inline-block', color: '#fff', fontSize: '20px', lineHeight: '27px', fontWeight: '500' }}>{displayName}</span>
 | 
			
		||||
          </a>
 | 
			
		||||
 | 
			
		||||
          <span style={{ fontSize: '14px', fontWeight: '400', display: 'block', color: '#2b90d9', marginBottom: '15px' }}>@{account.get('acct')}</span>
 | 
			
		||||
          <span style={{ fontSize: '14px', fontWeight: '400', display: 'block', color: '#2b90d9', marginBottom: '10px' }}>@{account.get('acct')}</span>
 | 
			
		||||
          <p style={{ color: '#616b86', fontSize: '14px' }}>{account.get('note')}</p>
 | 
			
		||||
 | 
			
		||||
          {info}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,7 @@ import {
 | 
			
		|||
import LoadingIndicator      from '../../components/loading_indicator';
 | 
			
		||||
import ActionBar             from './components/action_bar';
 | 
			
		||||
import Column                from '../ui/components/column';
 | 
			
		||||
import ColumnBackButton      from '../../components/column_back_button';
 | 
			
		||||
 | 
			
		||||
const mapStateToProps = (state, props) => ({
 | 
			
		||||
  account: getAccount(state, Number(props.params.accountId)),
 | 
			
		||||
| 
						 | 
				
			
			@ -74,6 +75,7 @@ const Account = React.createClass({
 | 
			
		|||
 | 
			
		||||
    return (
 | 
			
		||||
      <Column>
 | 
			
		||||
        <ColumnBackButton />
 | 
			
		||||
        <Header account={account} me={me} />
 | 
			
		||||
 | 
			
		||||
        <ActionBar account={account} me={me} onFollow={this.handleFollow} onBlock={this.handleBlock} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,8 @@ import {
 | 
			
		|||
  getStatusAncestors,
 | 
			
		||||
  getStatusDescendants
 | 
			
		||||
}                            from '../../selectors';
 | 
			
		||||
import { ScrollContainer }   from 'react-router-scroll';
 | 
			
		||||
import ColumnBackButton      from '../../components/column_back_button';
 | 
			
		||||
 | 
			
		||||
const mapStateToProps = (state, props) => ({
 | 
			
		||||
  status: getStatus(state, Number(props.params.statusId)),
 | 
			
		||||
| 
						 | 
				
			
			@ -81,14 +83,18 @@ const Status = React.createClass({
 | 
			
		|||
 | 
			
		||||
    return (
 | 
			
		||||
      <Column>
 | 
			
		||||
        <div style={{ overflowY: 'scroll', flex: '1 1 auto' }} className='scrollable'>
 | 
			
		||||
          <div>{this.renderChildren(ancestors)}</div>
 | 
			
		||||
        <ColumnBackButton />
 | 
			
		||||
 | 
			
		||||
          <DetailedStatus status={status} me={me} />
 | 
			
		||||
          <ActionBar status={status} me={me} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} />
 | 
			
		||||
        <ScrollContainer scrollKey='thread'>
 | 
			
		||||
          <div style={{ overflowY: 'scroll', flex: '1 1 auto' }} className='scrollable'>
 | 
			
		||||
            <div>{this.renderChildren(ancestors)}</div>
 | 
			
		||||
 | 
			
		||||
          <div>{this.renderChildren(descendants)}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
            <DetailedStatus status={status} me={me} />
 | 
			
		||||
            <ActionBar status={status} me={me} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} />
 | 
			
		||||
 | 
			
		||||
            <div>{this.renderChildren(descendants)}</div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </ScrollContainer>
 | 
			
		||||
      </Column>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -77,7 +77,7 @@ function normalizeStatus(state, status) {
 | 
			
		|||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function normalizeTimeline(state, timeline, statuses) {
 | 
			
		||||
function normalizeTimeline(state, timeline, statuses, replace = false) {
 | 
			
		||||
  let ids = Immutable.List([]);
 | 
			
		||||
 | 
			
		||||
  statuses.forEach((status, i) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -85,7 +85,7 @@ function normalizeTimeline(state, timeline, statuses) {
 | 
			
		|||
    ids   = ids.set(i, status.get('id'));
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return state.update(timeline, list => list.unshift(...ids));
 | 
			
		||||
  return state.update(timeline, list => (replace ? ids : list.unshift(...ids)));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function appendNormalizedTimeline(state, timeline, statuses) {
 | 
			
		||||
| 
						 | 
				
			
			@ -99,7 +99,7 @@ function appendNormalizedTimeline(state, timeline, statuses) {
 | 
			
		|||
  return state.update(timeline, list => list.push(...moreIds));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function normalizeAccountTimeline(state, accountId, statuses) {
 | 
			
		||||
function normalizeAccountTimeline(state, accountId, statuses, replace = false) {
 | 
			
		||||
  let ids = Immutable.List([]);
 | 
			
		||||
 | 
			
		||||
  statuses.forEach((status, i) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -107,7 +107,7 @@ function normalizeAccountTimeline(state, accountId, statuses) {
 | 
			
		|||
    ids   = ids.set(i, status.get('id'));
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return state.updateIn(['accounts_timelines', accountId], Immutable.List([]), list => list.unshift(...ids));
 | 
			
		||||
  return state.updateIn(['accounts_timelines', accountId], Immutable.List([]), list => (replace ? ids : list.unshift(...ids)));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function appendNormalizedAccountTimeline(state, accountId, statuses) {
 | 
			
		||||
| 
						 | 
				
			
			@ -217,7 +217,7 @@ function normalizeSuggestions(state, accounts) {
 | 
			
		|||
export default function timelines(state = initialState, action) {
 | 
			
		||||
  switch(action.type) {
 | 
			
		||||
    case TIMELINE_REFRESH_SUCCESS:
 | 
			
		||||
      return normalizeTimeline(state, action.timeline, Immutable.fromJS(action.statuses));
 | 
			
		||||
      return normalizeTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.replace);
 | 
			
		||||
    case TIMELINE_EXPAND_SUCCESS:
 | 
			
		||||
      return appendNormalizedTimeline(state, action.timeline, Immutable.fromJS(action.statuses));
 | 
			
		||||
    case TIMELINE_UPDATE:
 | 
			
		||||
| 
						 | 
				
			
			@ -243,7 +243,7 @@ export default function timelines(state = initialState, action) {
 | 
			
		|||
    case STATUS_FETCH_SUCCESS:
 | 
			
		||||
      return normalizeContext(state, Immutable.fromJS(action.status), Immutable.fromJS(action.context.ancestors), Immutable.fromJS(action.context.descendants));
 | 
			
		||||
    case ACCOUNT_TIMELINE_FETCH_SUCCESS:
 | 
			
		||||
      return normalizeAccountTimeline(state, action.id, Immutable.fromJS(action.statuses));
 | 
			
		||||
      return normalizeAccountTimeline(state, action.id, Immutable.fromJS(action.statuses), action.replace);
 | 
			
		||||
    case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
 | 
			
		||||
      return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses));
 | 
			
		||||
    case SUGGESTIONS_FETCH_SUCCESS:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,7 +37,7 @@ class ApiController < ApplicationController
 | 
			
		|||
  end
 | 
			
		||||
 | 
			
		||||
  def set_maps(statuses)
 | 
			
		||||
    status_ids      = statuses.flat_map { |s| [s.id, s.reblog_of_id] }.compact
 | 
			
		||||
    status_ids      = statuses.flat_map { |s| [s.id, s.reblog_of_id] }.compact.uniq
 | 
			
		||||
    @reblogs_map    = Status.reblogs_map(status_ids, current_user.account)
 | 
			
		||||
    @favourites_map = Status.favourites_map(status_ids, current_user.account)
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,13 +8,12 @@ class Feed
 | 
			
		|||
    max_id     = '+inf' if max_id.blank?
 | 
			
		||||
    since_id   = '-inf' if since_id.blank?
 | 
			
		||||
    unhydrated = redis.zrevrangebyscore(key, "(#{max_id}", "(#{since_id}", limit: [0, limit], with_scores: true).collect(&:last).map(&:to_i)
 | 
			
		||||
    status_map = {}
 | 
			
		||||
 | 
			
		||||
    # If we're after most recent items and none are there, we need to precompute the feed
 | 
			
		||||
    if unhydrated.empty? && max_id == '+inf'
 | 
			
		||||
    if unhydrated.empty? && max_id == '+inf' && since_id == '-inf'
 | 
			
		||||
      PrecomputeFeedService.new.call(@type, @account, limit)
 | 
			
		||||
    else
 | 
			
		||||
      Status.where(id: unhydrated).with_includes.with_counters.each { |status| status_map[status.id] = status }
 | 
			
		||||
      status_map = Status.where(id: unhydrated).with_includes.with_counters.map { |status| [status.id, status] }.to_h
 | 
			
		||||
      unhydrated.map { |id| status_map[id] }.compact
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue