about summary refs log tree commit diff
path: root/app/javascript
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript')
-rw-r--r--app/javascript/mastodon/actions/notifications.js15
-rw-r--r--app/javascript/mastodon/actions/streaming.js7
-rw-r--r--app/javascript/mastodon/actions/timelines.js25
-rw-r--r--app/javascript/mastodon/stream.js15
-rw-r--r--app/javascript/styles/mastodon/accounts.scss13
-rw-r--r--app/javascript/styles/mastodon/footer.scss2
6 files changed, 55 insertions, 22 deletions
diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js
index 393268811..641ad0e14 100644
--- a/app/javascript/mastodon/actions/notifications.js
+++ b/app/javascript/mastodon/actions/notifications.js
@@ -76,9 +76,14 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
 
 const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
 
-export function expandNotifications({ maxId } = {}) {
+const noOp = () => {};
+
+export function expandNotifications({ maxId } = {}, done = noOp) {
   return (dispatch, getState) => {
-    if (getState().getIn(['notifications', 'isLoading'])) {
+    const notifications = getState().get('notifications');
+
+    if (notifications.get('isLoading')) {
+      done();
       return;
     }
 
@@ -87,6 +92,10 @@ export function expandNotifications({ maxId } = {}) {
       exclude_types: excludeTypesFromSettings(getState()),
     };
 
+    if (!maxId && notifications.get('items').size > 0) {
+      params.since_id = notifications.getIn(['items', 0]);
+    }
+
     dispatch(expandNotificationsRequest());
 
     api(getState).get('/api/v1/notifications', { params }).then(response => {
@@ -97,8 +106,10 @@ export function expandNotifications({ maxId } = {}) {
 
       dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null));
       fetchRelatedRelationships(dispatch, response.data);
+      done();
     }).catch(error => {
       dispatch(expandNotificationsFail(error));
+      done();
     });
   };
 };
diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js
index 14215ab6d..10e68bf3a 100644
--- a/app/javascript/mastodon/actions/streaming.js
+++ b/app/javascript/mastodon/actions/streaming.js
@@ -36,10 +36,9 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null)
   });
 }
 
-function refreshHomeTimelineAndNotification (dispatch) {
-  dispatch(expandHomeTimeline());
-  dispatch(expandNotifications());
-}
+const refreshHomeTimelineAndNotification = (dispatch, done) => {
+  dispatch(expandHomeTimeline({}, () => dispatch(expandNotifications({}, done))));
+};
 
 export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
 export const connectCommunityStream = () => connectTimelineStream('community', 'public:local');
diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js
index eca847ee7..8bcfe4db9 100644
--- a/app/javascript/mastodon/actions/timelines.js
+++ b/app/javascript/mastodon/actions/timelines.js
@@ -1,6 +1,6 @@
 import { importFetchedStatus, importFetchedStatuses } from './importer';
 import api, { getLinks } from '../api';
-import { Map as ImmutableMap } from 'immutable';
+import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
 
 export const TIMELINE_UPDATE  = 'TIMELINE_UPDATE';
 export const TIMELINE_DELETE  = 'TIMELINE_DELETE';
@@ -64,35 +64,44 @@ export function deleteFromTimelines(id) {
   };
 };
 
-export function expandTimeline(timelineId, path, params = {}) {
+const noOp = () => {};
+
+export function expandTimeline(timelineId, path, params = {}, done = noOp) {
   return (dispatch, getState) => {
     const timeline = getState().getIn(['timelines', timelineId], ImmutableMap());
 
     if (timeline.get('isLoading')) {
+      done();
       return;
     }
 
+    if (!params.max_id && timeline.get('items', ImmutableList()).size > 0) {
+      params.since_id = timeline.getIn(['items', 0]);
+    }
+
     dispatch(expandTimelineRequest(timelineId));
 
     api(getState).get(path, { params }).then(response => {
       const next = getLinks(response).refs.find(link => link.rel === 'next');
       dispatch(importFetchedStatuses(response.data));
       dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206));
+      done();
     }).catch(error => {
       dispatch(expandTimelineFail(timelineId, error));
+      done();
     });
   };
 };
 
-export const expandHomeTimeline         = ({ maxId } = {}) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId });
-export const expandPublicTimeline       = ({ maxId } = {}) => expandTimeline('public', '/api/v1/timelines/public', { max_id: maxId });
-export const expandCommunityTimeline    = ({ maxId } = {}) => expandTimeline('community', '/api/v1/timelines/public', { local: true, max_id: maxId });
-export const expandDirectTimeline       = ({ maxId } = {}) => expandTimeline('direct', '/api/v1/timelines/direct', { max_id: maxId });
+export const expandHomeTimeline         = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done);
+export const expandPublicTimeline       = ({ maxId } = {}, done = noOp) => expandTimeline('public', '/api/v1/timelines/public', { max_id: maxId }, done);
+export const expandCommunityTimeline    = ({ maxId } = {}, done = noOp) => expandTimeline('community', '/api/v1/timelines/public', { local: true, max_id: maxId }, done);
+export const expandDirectTimeline       = ({ maxId } = {}, done = noOp) => expandTimeline('direct', '/api/v1/timelines/direct', { max_id: maxId }, done);
 export const expandAccountTimeline      = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
 export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
 export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true });
-export const expandHashtagTimeline      = (hashtag, { maxId } = {}) => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, { max_id: maxId });
-export const expandListTimeline         = (id, { maxId } = {}) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId });
+export const expandHashtagTimeline      = (hashtag, { maxId } = {}, done = noOp) => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, { max_id: maxId }, done);
+export const expandListTimeline         = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
 
 export function expandTimelineRequest(timeline) {
   return {
diff --git a/app/javascript/mastodon/stream.js b/app/javascript/mastodon/stream.js
index 6c67ba275..9928d0dd7 100644
--- a/app/javascript/mastodon/stream.js
+++ b/app/javascript/mastodon/stream.js
@@ -1,21 +1,24 @@
 import WebSocketClient from 'websocket.js';
 
+const randomIntUpTo = max => Math.floor(Math.random() * Math.floor(max));
+
 export function connectStream(path, pollingRefresh = null, callbacks = () => ({ onDisconnect() {}, onReceive() {} })) {
   return (dispatch, getState) => {
     const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']);
     const accessToken = getState().getIn(['meta', 'access_token']);
     const { onDisconnect, onReceive } = callbacks(dispatch, getState);
+
     let polling = null;
 
     const setupPolling = () => {
-      polling = setInterval(() => {
-        pollingRefresh(dispatch);
-      }, 20000);
+      pollingRefresh(dispatch, () => {
+        polling = setTimeout(() => setupPolling(), 20000 + randomIntUpTo(20000));
+      });
     };
 
     const clearPolling = () => {
       if (polling) {
-        clearInterval(polling);
+        clearTimeout(polling);
         polling = null;
       }
     };
@@ -29,8 +32,9 @@ export function connectStream(path, pollingRefresh = null, callbacks = () => ({
 
       disconnected () {
         if (pollingRefresh) {
-          setupPolling();
+          polling = setTimeout(() => setupPolling(), randomIntUpTo(40000));
         }
+
         onDisconnect();
       },
 
@@ -51,6 +55,7 @@ export function connectStream(path, pollingRefresh = null, callbacks = () => ({
       if (subscription) {
         subscription.close();
       }
+
       clearPolling();
     };
 
diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss
index b063ca52d..93aa134cf 100644
--- a/app/javascript/styles/mastodon/accounts.scss
+++ b/app/javascript/styles/mastodon/accounts.scss
@@ -322,6 +322,15 @@
   z-index: 2;
   position: relative;
 
+  &.empty img {
+    position: absolute;
+    opacity: 0.2;
+    height: 200px;
+    left: 0;
+    bottom: 0;
+    pointer-events: none;
+  }
+
   @media screen and (max-width: 740px) {
     border-radius: 0;
     box-shadow: none;
@@ -438,8 +447,8 @@
   font-size: 14px;
   font-weight: 500;
   text-align: center;
-  padding: 60px 0;
-  padding-top: 55px;
+  padding: 130px 0;
+  padding-top: 125px;
   margin: 0 auto;
   cursor: default;
 }
diff --git a/app/javascript/styles/mastodon/footer.scss b/app/javascript/styles/mastodon/footer.scss
index ba2a06954..dd3c1b688 100644
--- a/app/javascript/styles/mastodon/footer.scss
+++ b/app/javascript/styles/mastodon/footer.scss
@@ -4,7 +4,7 @@
   font-size: 12px;
   color: $darker-text-color;
 
-  .domain {
+  .footer__domain {
     font-weight: 500;
 
     a {