about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2016-10-18 23:06:28 +0200
committerEugen Rochko <eugen@zeonfederated.com>2016-10-18 23:06:28 +0200
commit8698cd3281ac1d699c723a151b14f1e2f2e8b07e (patch)
tree00365bb754c9322615538fdef2d9c8adbde3bc11 /app
parent1d2175f73c8e6e92768ae51f66cb94d64d2265a7 (diff)
Remember scroll position when navigating back, do not needlessly reload
entire timelines (only fetch since last known ID). Side effect: account
timelines no longer update in real-time
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/components/actions/accounts.jsx11
-rw-r--r--app/assets/javascripts/components/actions/timelines.jsx11
-rw-r--r--app/assets/javascripts/components/components/status_list.jsx28
-rw-r--r--app/assets/javascripts/components/containers/mastodon.jsx4
-rw-r--r--app/assets/javascripts/components/features/home_timeline/index.jsx2
-rw-r--r--app/assets/javascripts/components/features/mentions_timeline/index.jsx2
-rw-r--r--app/assets/javascripts/components/features/ui/index.jsx4
-rw-r--r--app/assets/javascripts/components/reducers/timelines.jsx12
8 files changed, 55 insertions, 19 deletions
diff --git a/app/assets/javascripts/components/actions/accounts.jsx b/app/assets/javascripts/components/actions/accounts.jsx
index 4847c37e2..c1c99d6bd 100644
--- a/app/assets/javascripts/components/actions/accounts.jsx
+++ b/app/assets/javascripts/components/actions/accounts.jsx
@@ -57,7 +57,16 @@ export function fetchAccountTimeline(id) {
   return (dispatch, getState) => {
     dispatch(fetchAccountTimelineRequest(id));
 
-    api(getState).get(`/api/v1/accounts/${id}/statuses`).then(response => {
+    const ids      = getState().getIn(['timelines', 'accounts_timelines', id], Immutable.List());
+    const newestId = ids.size > 0 ? ids.first() : null;
+
+    let params = '';
+
+    if (newestId !== null) {
+      params = `?since_id=${newestId}`;
+    }
+
+    api(getState).get(`/api/v1/accounts/${id}/statuses${params}`).then(response => {
       dispatch(fetchAccountTimelineSuccess(id, response.data));
     }).catch(error => {
       dispatch(fetchAccountTimelineFail(id, error));
diff --git a/app/assets/javascripts/components/actions/timelines.jsx b/app/assets/javascripts/components/actions/timelines.jsx
index f92f758f5..5258d7103 100644
--- a/app/assets/javascripts/components/actions/timelines.jsx
+++ b/app/assets/javascripts/components/actions/timelines.jsx
@@ -45,7 +45,16 @@ export function refreshTimeline(timeline) {
   return function (dispatch, getState) {
     dispatch(refreshTimelineRequest(timeline));
 
-    api(getState).get(`/api/v1/statuses/${timeline}`).then(function (response) {
+    const ids      = getState().getIn(['timelines', timeline]);
+    const newestId = ids.size > 0 ? ids.first() : null;
+
+    let params = '';
+
+    if (newestId !== null) {
+      params = `?since_id=${newestId}`;
+    }
+
+    api(getState).get(`/api/v1/statuses/${timeline}${params}`).then(function (response) {
       dispatch(refreshTimelineSuccess(timeline, response.data));
     }).catch(function (error) {
       dispatch(refreshTimelineFail(timeline, error));
diff --git a/app/assets/javascripts/components/components/status_list.jsx b/app/assets/javascripts/components/components/status_list.jsx
index f70d53263..4977d84ce 100644
--- a/app/assets/javascripts/components/components/status_list.jsx
+++ b/app/assets/javascripts/components/components/status_list.jsx
@@ -1,6 +1,7 @@
-import Status             from './status';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PureRenderMixin    from 'react-addons-pure-render-mixin';
+import Status              from './status';
+import ImmutablePropTypes  from 'react-immutable-proptypes';
+import PureRenderMixin     from 'react-addons-pure-render-mixin';
+import { ScrollContainer } from 'react-router-scroll';
 
 const StatusList = React.createClass({
 
@@ -11,9 +12,16 @@ const StatusList = React.createClass({
     onFavourite: React.PropTypes.func,
     onDelete: React.PropTypes.func,
     onScrollToBottom: React.PropTypes.func,
+    trackScroll: React.PropTypes.bool,
     me: React.PropTypes.number
   },
 
+  getDefaultProps () {
+    return {
+      trackScroll: true
+    };
+  },
+
   mixins: [PureRenderMixin],
 
   handleScroll (e) {
@@ -25,9 +33,9 @@ const StatusList = React.createClass({
   },
 
   render () {
-    const { statuses, onScrollToBottom, ...other } = this.props;
+    const { statuses, onScrollToBottom, trackScroll, ...other } = this.props;
 
-    return (
+    const scrollableArea = (
       <div style={{ overflowY: 'scroll', flex: '1 1 auto', overflowX: 'hidden' }} className='scrollable' onScroll={this.handleScroll}>
         <div>
           {statuses.map((status) => {
@@ -36,6 +44,16 @@ const StatusList = React.createClass({
         </div>
       </div>
     );
+
+    if (trackScroll) {
+      return (
+        <ScrollContainer scrollKey='status-list'>
+          {scrollableArea}
+        </ScrollContainer>
+      );
+    } else {
+      return scrollableArea;
+    }
   }
 
 });
diff --git a/app/assets/javascripts/components/containers/mastodon.jsx b/app/assets/javascripts/components/containers/mastodon.jsx
index e5c0887a9..4eb9f83c8 100644
--- a/app/assets/javascripts/components/containers/mastodon.jsx
+++ b/app/assets/javascripts/components/containers/mastodon.jsx
@@ -10,11 +10,13 @@ import { setAccessToken } from '../actions/meta';
 import { setAccountSelf } from '../actions/accounts';
 import PureRenderMixin    from 'react-addons-pure-render-mixin';
 import {
+  applyRouterMiddleware,
   Router,
   Route,
   hashHistory,
   IndexRoute
 }                         from 'react-router';
+import { useScroll }      from 'react-router-scroll';
 import UI                 from '../features/ui';
 import Account            from '../features/account';
 import Status             from '../features/status';
@@ -71,7 +73,7 @@ const Mastodon = React.createClass({
   render () {
     return (
       <Provider store={store}>
-        <Router history={hashHistory}>
+        <Router history={hashHistory} render={applyRouterMiddleware(useScroll())}>
           <Route path='/' component={UI}>
             <IndexRoute component={GettingStarted} />
             <Route path='/statuses/new' component={Compose} />
diff --git a/app/assets/javascripts/components/features/home_timeline/index.jsx b/app/assets/javascripts/components/features/home_timeline/index.jsx
index 9be3f3964..e4cd8bdca 100644
--- a/app/assets/javascripts/components/features/home_timeline/index.jsx
+++ b/app/assets/javascripts/components/features/home_timeline/index.jsx
@@ -19,7 +19,7 @@ const HomeTimeline = React.createClass({
   render () {
     return (
       <Column icon='home' heading='Home'>
-        <StatusListContainer type='home' />
+        <StatusListContainer {...this.props} type='home' />
       </Column>
     );
   },
diff --git a/app/assets/javascripts/components/features/mentions_timeline/index.jsx b/app/assets/javascripts/components/features/mentions_timeline/index.jsx
index a1b511d3e..919a75d18 100644
--- a/app/assets/javascripts/components/features/mentions_timeline/index.jsx
+++ b/app/assets/javascripts/components/features/mentions_timeline/index.jsx
@@ -19,7 +19,7 @@ const MentionsTimeline = React.createClass({
   render () {
     return (
       <Column icon='at' heading='Mentions'>
-        <StatusListContainer type='mentions' />
+        <StatusListContainer {...this.props} type='mentions' />
       </Column>
     );
   },
diff --git a/app/assets/javascripts/components/features/ui/index.jsx b/app/assets/javascripts/components/features/ui/index.jsx
index fab32a31e..06a9d2f50 100644
--- a/app/assets/javascripts/components/features/ui/index.jsx
+++ b/app/assets/javascripts/components/features/ui/index.jsx
@@ -28,8 +28,8 @@ const UI = React.createClass({
         <MediaQuery minWidth={layoutBreakpoint}>
           <ColumnsArea>
             <Compose />
-            <HomeTimeline />
-            <MentionsTimeline />
+            <HomeTimeline trackScroll={false} />
+            <MentionsTimeline trackScroll={false} />
             {this.props.children}
           </ColumnsArea>
         </MediaQuery>
diff --git a/app/assets/javascripts/components/reducers/timelines.jsx b/app/assets/javascripts/components/reducers/timelines.jsx
index 65bccb44d..06534971d 100644
--- a/app/assets/javascripts/components/reducers/timelines.jsx
+++ b/app/assets/javascripts/components/reducers/timelines.jsx
@@ -85,7 +85,7 @@ function normalizeTimeline(state, timeline, statuses) {
     ids   = ids.set(i, status.get('id'));
   });
 
-  return state.set(timeline, ids);
+  return state.update(timeline, list => list.unshift(...ids));
 };
 
 function appendNormalizedTimeline(state, timeline, statuses) {
@@ -100,16 +100,14 @@ function appendNormalizedTimeline(state, timeline, statuses) {
 };
 
 function normalizeAccountTimeline(state, accountId, statuses) {
-  state = state.updateIn(['accounts_timelines', accountId], Immutable.List([]), list => {
-    return (list.size > 0) ? list.clear() : list;
-  });
+  let ids = Immutable.List([]);
 
   statuses.forEach((status, i) => {
     state = normalizeStatus(state, status);
-    state = state.updateIn(['accounts_timelines', accountId], Immutable.List([]), list => list.set(i, status.get('id')));
+    ids   = ids.set(i, status.get('id'));
   });
 
-  return state;
+  return state.updateIn(['accounts_timelines', accountId], Immutable.List([]), list => list.unshift(...ids));
 };
 
 function appendNormalizedAccountTimeline(state, accountId, statuses) {
@@ -137,7 +135,7 @@ function updateTimeline(state, timeline, status) {
     return list.unshift(status.get('id'));
   });
 
-  state = state.updateIn(['accounts_timelines', status.getIn(['account', 'id'])], Immutable.List([]), list => (list.includes(status.get('id')) ? list : list.unshift(status.get('id'))));
+  //state = state.updateIn(['accounts_timelines', status.getIn(['account', 'id'])], Immutable.List([]), list => (list.includes(status.get('id')) ? list : list.unshift(status.get('id'))));
 
   return state;
 };