mirror of
https://github.com/whippyshou/mastodon
synced 2024-11-25 15:46:35 +09:00
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
@ -53,7 +53,7 @@ export function fetchAccount(id) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function fetchAccountTimeline(id) {
|
export function fetchAccountTimeline(id, replace = false) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(fetchAccountTimelineRequest(id));
|
dispatch(fetchAccountTimelineRequest(id));
|
||||||
|
|
||||||
@ -62,12 +62,12 @@ export function fetchAccountTimeline(id) {
|
|||||||
|
|
||||||
let params = '';
|
let params = '';
|
||||||
|
|
||||||
if (newestId !== null) {
|
if (newestId !== null && !replace) {
|
||||||
params = `?since_id=${newestId}`;
|
params = `?since_id=${newestId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
api(getState).get(`/api/v1/accounts/${id}/statuses${params}`).then(response => {
|
api(getState).get(`/api/v1/accounts/${id}/statuses${params}`).then(response => {
|
||||||
dispatch(fetchAccountTimelineSuccess(id, response.data));
|
dispatch(fetchAccountTimelineSuccess(id, response.data, replace));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(fetchAccountTimelineFail(id, 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 {
|
return {
|
||||||
type: ACCOUNT_TIMELINE_FETCH_SUCCESS,
|
type: ACCOUNT_TIMELINE_FETCH_SUCCESS,
|
||||||
id: id,
|
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_SUCCESS = 'TIMELINE_EXPAND_SUCCESS';
|
||||||
export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL';
|
export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL';
|
||||||
|
|
||||||
export function refreshTimelineSuccess(timeline, statuses) {
|
export function refreshTimelineSuccess(timeline, statuses, replace) {
|
||||||
return {
|
return {
|
||||||
type: TIMELINE_REFRESH_SUCCESS,
|
type: TIMELINE_REFRESH_SUCCESS,
|
||||||
timeline: timeline,
|
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) {
|
return function (dispatch, getState) {
|
||||||
dispatch(refreshTimelineRequest(timeline));
|
dispatch(refreshTimelineRequest(timeline));
|
||||||
|
|
||||||
@ -50,12 +51,12 @@ export function refreshTimeline(timeline) {
|
|||||||
|
|
||||||
let params = '';
|
let params = '';
|
||||||
|
|
||||||
if (newestId !== null) {
|
if (newestId !== null && !replace) {
|
||||||
params = `?since_id=${newestId}`;
|
params = `?since_id=${newestId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
api(getState).get(`/api/v1/statuses/${timeline}${params}`).then(function (response) {
|
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) {
|
}).catch(function (error) {
|
||||||
dispatch(refreshTimelineFail(timeline, 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));
|
return store.dispatch(deleteFromTimelines(data.id));
|
||||||
case 'merge':
|
case 'merge':
|
||||||
case 'unmerge':
|
case 'unmerge':
|
||||||
return store.dispatch(refreshTimeline('home'));
|
return store.dispatch(refreshTimeline('home', true));
|
||||||
case 'block':
|
case 'block':
|
||||||
return store.dispatch(refreshTimeline('mentions'));
|
return store.dispatch(refreshTimeline('mentions', true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,16 +26,16 @@ const Header = React.createClass({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ flex: '0 0 auto', background: '#2f3441', textAlign: 'center', backgroundImage: `url(${account.get('header')})`, backgroundSize: 'cover', position: 'relative' }}>
|
<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' }}>
|
<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' }} />
|
<img src={account.get('avatar')} alt='' style={{ display: 'block', width: '90px', height: '90px', borderRadius: '90px' }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span style={{ display: 'inline-block', color: '#fff', fontSize: '20px', lineHeight: '27px', fontWeight: '500' }}>{displayName}</span>
|
<span style={{ display: 'inline-block', color: '#fff', fontSize: '20px', lineHeight: '27px', fontWeight: '500' }}>{displayName}</span>
|
||||||
</a>
|
</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>
|
<p style={{ color: '#616b86', fontSize: '14px' }}>{account.get('note')}</p>
|
||||||
|
|
||||||
{info}
|
{info}
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
import LoadingIndicator from '../../components/loading_indicator';
|
import LoadingIndicator from '../../components/loading_indicator';
|
||||||
import ActionBar from './components/action_bar';
|
import ActionBar from './components/action_bar';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
|
import ColumnBackButton from '../../components/column_back_button';
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
account: getAccount(state, Number(props.params.accountId)),
|
account: getAccount(state, Number(props.params.accountId)),
|
||||||
@ -74,6 +75,7 @@ const Account = React.createClass({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
|
<ColumnBackButton />
|
||||||
<Header account={account} me={me} />
|
<Header account={account} me={me} />
|
||||||
|
|
||||||
<ActionBar account={account} me={me} onFollow={this.handleFollow} onBlock={this.handleBlock} />
|
<ActionBar account={account} me={me} onFollow={this.handleFollow} onBlock={this.handleBlock} />
|
||||||
|
@ -16,6 +16,8 @@ import {
|
|||||||
getStatusAncestors,
|
getStatusAncestors,
|
||||||
getStatusDescendants
|
getStatusDescendants
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
|
import { ScrollContainer } from 'react-router-scroll';
|
||||||
|
import ColumnBackButton from '../../components/column_back_button';
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
status: getStatus(state, Number(props.params.statusId)),
|
status: getStatus(state, Number(props.params.statusId)),
|
||||||
@ -81,14 +83,18 @@ const Status = React.createClass({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<div style={{ overflowY: 'scroll', flex: '1 1 auto' }} className='scrollable'>
|
<ColumnBackButton />
|
||||||
<div>{this.renderChildren(ancestors)}</div>
|
|
||||||
|
|
||||||
<DetailedStatus status={status} me={me} />
|
<ScrollContainer scrollKey='thread'>
|
||||||
<ActionBar status={status} me={me} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} />
|
<div style={{ overflowY: 'scroll', flex: '1 1 auto' }} className='scrollable'>
|
||||||
|
<div>{this.renderChildren(ancestors)}</div>
|
||||||
|
|
||||||
<div>{this.renderChildren(descendants)}</div>
|
<DetailedStatus status={status} me={me} />
|
||||||
</div>
|
<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>
|
</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([]);
|
let ids = Immutable.List([]);
|
||||||
|
|
||||||
statuses.forEach((status, i) => {
|
statuses.forEach((status, i) => {
|
||||||
@ -85,7 +85,7 @@ function normalizeTimeline(state, timeline, statuses) {
|
|||||||
ids = ids.set(i, status.get('id'));
|
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) {
|
function appendNormalizedTimeline(state, timeline, statuses) {
|
||||||
@ -99,7 +99,7 @@ function appendNormalizedTimeline(state, timeline, statuses) {
|
|||||||
return state.update(timeline, list => list.push(...moreIds));
|
return state.update(timeline, list => list.push(...moreIds));
|
||||||
};
|
};
|
||||||
|
|
||||||
function normalizeAccountTimeline(state, accountId, statuses) {
|
function normalizeAccountTimeline(state, accountId, statuses, replace = false) {
|
||||||
let ids = Immutable.List([]);
|
let ids = Immutable.List([]);
|
||||||
|
|
||||||
statuses.forEach((status, i) => {
|
statuses.forEach((status, i) => {
|
||||||
@ -107,7 +107,7 @@ function normalizeAccountTimeline(state, accountId, statuses) {
|
|||||||
ids = ids.set(i, status.get('id'));
|
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) {
|
function appendNormalizedAccountTimeline(state, accountId, statuses) {
|
||||||
@ -217,7 +217,7 @@ function normalizeSuggestions(state, accounts) {
|
|||||||
export default function timelines(state = initialState, action) {
|
export default function timelines(state = initialState, action) {
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case TIMELINE_REFRESH_SUCCESS:
|
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:
|
case TIMELINE_EXPAND_SUCCESS:
|
||||||
return appendNormalizedTimeline(state, action.timeline, Immutable.fromJS(action.statuses));
|
return appendNormalizedTimeline(state, action.timeline, Immutable.fromJS(action.statuses));
|
||||||
case TIMELINE_UPDATE:
|
case TIMELINE_UPDATE:
|
||||||
@ -243,7 +243,7 @@ export default function timelines(state = initialState, action) {
|
|||||||
case STATUS_FETCH_SUCCESS:
|
case STATUS_FETCH_SUCCESS:
|
||||||
return normalizeContext(state, Immutable.fromJS(action.status), Immutable.fromJS(action.context.ancestors), Immutable.fromJS(action.context.descendants));
|
return normalizeContext(state, Immutable.fromJS(action.status), Immutable.fromJS(action.context.ancestors), Immutable.fromJS(action.context.descendants));
|
||||||
case ACCOUNT_TIMELINE_FETCH_SUCCESS:
|
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:
|
case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
|
||||||
return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses));
|
return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses));
|
||||||
case SUGGESTIONS_FETCH_SUCCESS:
|
case SUGGESTIONS_FETCH_SUCCESS:
|
||||||
|
@ -37,7 +37,7 @@ class ApiController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def set_maps(statuses)
|
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)
|
@reblogs_map = Status.reblogs_map(status_ids, current_user.account)
|
||||||
@favourites_map = Status.favourites_map(status_ids, current_user.account)
|
@favourites_map = Status.favourites_map(status_ids, current_user.account)
|
||||||
end
|
end
|
||||||
|
@ -8,13 +8,12 @@ class Feed
|
|||||||
max_id = '+inf' if max_id.blank?
|
max_id = '+inf' if max_id.blank?
|
||||||
since_id = '-inf' if since_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)
|
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 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)
|
PrecomputeFeedService.new.call(@type, @account, limit)
|
||||||
else
|
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
|
unhydrated.map { |id| status_map[id] }.compact
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user