diff --git a/app/assets/javascripts/components/actions/accounts.jsx b/app/assets/javascripts/components/actions/accounts.jsx
index c1c99d6bd8..eacbeef06f 100644
--- a/app/assets/javascripts/components/actions/accounts.jsx
+++ b/app/assets/javascripts/components/actions/accounts.jsx
@@ -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
};
};
diff --git a/app/assets/javascripts/components/actions/timelines.jsx b/app/assets/javascripts/components/actions/timelines.jsx
index 5258d7103e..831065febf 100644
--- a/app/assets/javascripts/components/actions/timelines.jsx
+++ b/app/assets/javascripts/components/actions/timelines.jsx
@@ -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));
});
diff --git a/app/assets/javascripts/components/components/column_back_button.jsx b/app/assets/javascripts/components/components/column_back_button.jsx
new file mode 100644
index 0000000000..755378ad82
--- /dev/null
+++ b/app/assets/javascripts/components/components/column_back_button.jsx
@@ -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 (
+
+
+ Back
+
+ );
+ }
+
+});
+
+export default ColumnBackButton;
diff --git a/app/assets/javascripts/components/containers/mastodon.jsx b/app/assets/javascripts/components/containers/mastodon.jsx
index 4eb9f83c8d..8e1becbda3 100644
--- a/app/assets/javascripts/components/containers/mastodon.jsx
+++ b/app/assets/javascripts/components/containers/mastodon.jsx
@@ -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));
}
}
diff --git a/app/assets/javascripts/components/features/account/components/header.jsx b/app/assets/javascripts/components/features/account/components/header.jsx
index 681b30ca88..d794a0aaf1 100644
--- a/app/assets/javascripts/components/features/account/components/header.jsx
+++ b/app/assets/javascripts/components/features/account/components/header.jsx
@@ -26,16 +26,16 @@ const Header = React.createClass({
return (
-
+
-
+
{displayName}
-
@{account.get('acct')}
+
@{account.get('acct')}
{account.get('note')}
{info}
diff --git a/app/assets/javascripts/components/features/account/index.jsx b/app/assets/javascripts/components/features/account/index.jsx
index 22e02ff549..83770eb749 100644
--- a/app/assets/javascripts/components/features/account/index.jsx
+++ b/app/assets/javascripts/components/features/account/index.jsx
@@ -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 (
+
diff --git a/app/assets/javascripts/components/features/status/index.jsx b/app/assets/javascripts/components/features/status/index.jsx
index cc7a2bfebb..c51fb5d317 100644
--- a/app/assets/javascripts/components/features/status/index.jsx
+++ b/app/assets/javascripts/components/features/status/index.jsx
@@ -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 (
-
-
{this.renderChildren(ancestors)}
+
-
-
+
+
+
{this.renderChildren(ancestors)}
-
{this.renderChildren(descendants)}
-
+
+
+
+ {this.renderChildren(descendants)}
+
+
);
}
diff --git a/app/assets/javascripts/components/reducers/timelines.jsx b/app/assets/javascripts/components/reducers/timelines.jsx
index 06534971d2..331cbf59c6 100644
--- a/app/assets/javascripts/components/reducers/timelines.jsx
+++ b/app/assets/javascripts/components/reducers/timelines.jsx
@@ -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:
diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb
index c4ad450cf9..e29892cbed 100644
--- a/app/controllers/api_controller.rb
+++ b/app/controllers/api_controller.rb
@@ -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
diff --git a/app/models/feed.rb b/app/models/feed.rb
index 4466ea14ed..2bc9e980a2 100644
--- a/app/models/feed.rb
+++ b/app/models/feed.rb
@@ -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