about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/reducers
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours/glitch/reducers')
-rw-r--r--app/javascript/flavours/glitch/reducers/announcements.js102
-rw-r--r--app/javascript/flavours/glitch/reducers/compose.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/index.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/local_settings.js1
-rw-r--r--app/javascript/flavours/glitch/reducers/notifications.js19
-rw-r--r--app/javascript/flavours/glitch/reducers/statuses.js3
6 files changed, 125 insertions, 4 deletions
diff --git a/app/javascript/flavours/glitch/reducers/announcements.js b/app/javascript/flavours/glitch/reducers/announcements.js
new file mode 100644
index 000000000..34e08eac8
--- /dev/null
+++ b/app/javascript/flavours/glitch/reducers/announcements.js
@@ -0,0 +1,102 @@
+import {
+  ANNOUNCEMENTS_FETCH_REQUEST,
+  ANNOUNCEMENTS_FETCH_SUCCESS,
+  ANNOUNCEMENTS_FETCH_FAIL,
+  ANNOUNCEMENTS_UPDATE,
+  ANNOUNCEMENTS_REACTION_UPDATE,
+  ANNOUNCEMENTS_REACTION_ADD_REQUEST,
+  ANNOUNCEMENTS_REACTION_ADD_FAIL,
+  ANNOUNCEMENTS_REACTION_REMOVE_REQUEST,
+  ANNOUNCEMENTS_REACTION_REMOVE_FAIL,
+  ANNOUNCEMENTS_TOGGLE_SHOW,
+  ANNOUNCEMENTS_DELETE,
+  ANNOUNCEMENTS_DISMISS_SUCCESS,
+} from '../actions/announcements';
+import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
+
+const initialState = ImmutableMap({
+  items: ImmutableList(),
+  isLoading: false,
+  show: false,
+});
+
+const updateReaction = (state, id, name, updater) => state.update('items', list => list.map(announcement => {
+  if (announcement.get('id') === id) {
+    return announcement.update('reactions', reactions => {
+      const idx = reactions.findIndex(reaction => reaction.get('name') === name);
+
+      if (idx > -1) {
+        return reactions.update(idx, reaction => updater(reaction));
+      }
+
+      return reactions.push(updater(fromJS({ name, count: 0 })));
+    });
+  }
+
+  return announcement;
+}));
+
+const updateReactionCount = (state, reaction) => updateReaction(state, reaction.announcement_id, reaction.name, x => x.set('count', reaction.count));
+
+const addReaction = (state, id, name) => updateReaction(state, id, name, x => x.set('me', true).update('count', y => y + 1));
+
+const removeReaction = (state, id, name) => updateReaction(state, id, name, x => x.set('me', false).update('count', y => y - 1));
+
+const sortAnnouncements = list => list.sortBy(x => x.get('starts_at') || x.get('published_at'));
+
+const updateAnnouncement = (state, announcement) => {
+  const idx = state.get('items').findIndex(x => x.get('id') === announcement.get('id'));
+
+  if (idx > -1) {
+    // Deep merge is used because announcements from the streaming API do not contain
+    // personalized data about which reactions have been selected by the given user,
+    // and that is information we want to preserve
+    return state.update('items', list => sortAnnouncements(list.update(idx, x => x.mergeDeep(announcement))));
+  }
+
+  return state.update('items', list => sortAnnouncements(list.unshift(announcement)));
+};
+
+export default function announcementsReducer(state = initialState, action) {
+  switch(action.type) {
+  case ANNOUNCEMENTS_TOGGLE_SHOW:
+    return state.withMutations(map => {
+      map.set('show', !map.get('show'));
+    });
+  case ANNOUNCEMENTS_FETCH_REQUEST:
+    return state.set('isLoading', true);
+  case ANNOUNCEMENTS_FETCH_SUCCESS:
+    return state.withMutations(map => {
+      const items = fromJS(action.announcements);
+
+      map.set('items', items);
+      map.set('isLoading', false);
+    });
+  case ANNOUNCEMENTS_FETCH_FAIL:
+    return state.set('isLoading', false);
+  case ANNOUNCEMENTS_UPDATE:
+    return updateAnnouncement(state, fromJS(action.announcement));
+  case ANNOUNCEMENTS_REACTION_UPDATE:
+    return updateReactionCount(state, action.reaction);
+  case ANNOUNCEMENTS_REACTION_ADD_REQUEST:
+  case ANNOUNCEMENTS_REACTION_REMOVE_FAIL:
+    return addReaction(state, action.id, action.name);
+  case ANNOUNCEMENTS_REACTION_REMOVE_REQUEST:
+  case ANNOUNCEMENTS_REACTION_ADD_FAIL:
+    return removeReaction(state, action.id, action.name);
+  case ANNOUNCEMENTS_DISMISS_SUCCESS:
+    return updateAnnouncement(state, fromJS({ 'id': action.id, 'read': true }));
+  case ANNOUNCEMENTS_DELETE:
+    return state.update('items', list => {
+      const idx = list.findIndex(x => x.get('id') === action.id);
+
+      if (idx > -1) {
+        return list.delete(idx);
+      }
+
+      return list;
+    });
+  default:
+    return state;
+  }
+};
diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js
index 0f807790b..92a9859d9 100644
--- a/app/javascript/flavours/glitch/reducers/compose.js
+++ b/app/javascript/flavours/glitch/reducers/compose.js
@@ -387,7 +387,7 @@ export default function compose(state = initialState, action) {
 
       if (action.status.get('spoiler_text').length > 0) {
         let spoiler_text = action.status.get('spoiler_text');
-        if (!spoiler_text.match(/^re[: ]/i)) {
+        if (action.prependCWRe && !spoiler_text.match(/^re[: ]/i)) {
           spoiler_text = 're: '.concat(spoiler_text);
         }
         map.set('spoiler', true);
diff --git a/app/javascript/flavours/glitch/reducers/index.js b/app/javascript/flavours/glitch/reducers/index.js
index 7dbca3a29..586b84749 100644
--- a/app/javascript/flavours/glitch/reducers/index.js
+++ b/app/javascript/flavours/glitch/reducers/index.js
@@ -35,8 +35,10 @@ import pinnedAccountsEditor from './pinned_accounts_editor';
 import polls from './polls';
 import identity_proofs from './identity_proofs';
 import trends from './trends';
+import announcements from './announcements';
 
 const reducers = {
+  announcements,
   dropdown_menu,
   timelines,
   meta,
diff --git a/app/javascript/flavours/glitch/reducers/local_settings.js b/app/javascript/flavours/glitch/reducers/local_settings.js
index ad94ea243..3d94d665c 100644
--- a/app/javascript/flavours/glitch/reducers/local_settings.js
+++ b/app/javascript/flavours/glitch/reducers/local_settings.js
@@ -17,6 +17,7 @@ const initialState = ImmutableMap({
   confirm_missing_media_description: false,
   confirm_boost_missing_media_description: false,
   confirm_before_clearing_draft: true,
+  prepend_cw_re: true,
   preselect_on_reply: true,
   inline_preview_cards: true,
   hicolor_privacy_icons: false,
diff --git a/app/javascript/flavours/glitch/reducers/notifications.js b/app/javascript/flavours/glitch/reducers/notifications.js
index 3623e90da..d0eb7bb5a 100644
--- a/app/javascript/flavours/glitch/reducers/notifications.js
+++ b/app/javascript/flavours/glitch/reducers/notifications.js
@@ -23,6 +23,9 @@ import {
   FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
   FOLLOW_REQUEST_REJECT_SUCCESS,
 } from 'flavours/glitch/actions/accounts';
+import {
+  MARKERS_FETCH_SUCCESS,
+} from 'flavours/glitch/actions/markers';
 import { DOMAIN_BLOCK_SUCCESS } from 'flavours/glitch/actions/domain_blocks';
 import { TIMELINE_DELETE, TIMELINE_DISCONNECT } from 'flavours/glitch/actions/timelines';
 import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
@@ -104,7 +107,7 @@ const expandNormalizedNotifications = (state, notifications, next, isLoadingRece
         mutable.update('lastReadId', id => compareId(id, items.first().get('id')) > 0 ? id : items.first().get('id'));
       }
     } else {
-      mutable.update('unread', unread => unread + items.filter(item => compareId(item.get('id'), lastReadId) > 0).size);
+      mutable.update('unread', unread => unread + items.count(item => compareId(item.get('id'), lastReadId) > 0));
     }
 
     if (!next) {
@@ -197,10 +200,24 @@ const shouldCountUnreadNotifications = (state) => {
   return !(state.get('isTabVisible') && state.get('top') && state.get('mounted') > 0);
 };
 
+const recountUnread = (state, last_read_id) => {
+  return state.withMutations(mutable => {
+    if (compareId(last_read_id, mutable.get('lastReadId')) > 0) {
+      mutable.set('lastReadId', last_read_id);
+    }
+
+    if (state.get('unread') > 0 || shouldCountUnreadNotifications(state)) {
+      mutable.set('unread', mutable.get('pendingItems').count(item => item !== null) + mutable.get('items').count(item => item && compareId(item.get('id'), last_read_id) > 0));
+    }
+  });
+}
+
 export default function notifications(state = initialState, action) {
   let st;
 
   switch(action.type) {
+  case MARKERS_FETCH_SUCCESS:
+    return action.markers.notifications ? recountUnread(state, action.markers.notifications.last_read_id) : state;
   case NOTIFICATIONS_MOUNT:
     return updateMounted(state);
   case NOTIFICATIONS_UNMOUNT:
diff --git a/app/javascript/flavours/glitch/reducers/statuses.js b/app/javascript/flavours/glitch/reducers/statuses.js
index ee8ac929d..8926e49f1 100644
--- a/app/javascript/flavours/glitch/reducers/statuses.js
+++ b/app/javascript/flavours/glitch/reducers/statuses.js
@@ -41,8 +41,7 @@ export default function statuses(state = initialState, action) {
   case FAVOURITE_REQUEST:
     return state.setIn([action.status.get('id'), 'favourited'], true);
   case UNFAVOURITE_SUCCESS:
-    const favouritesCount = action.status.get('favourites_count');
-    return state.setIn([action.status.get('id'), 'favourites_count'], favouritesCount - 1);
+    return state.updateIn([action.status.get('id'), 'favourites_count'], x => Math.max(0, x - 1));
   case FAVOURITE_FAIL:
     return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'favourited'], false);
   case BOOKMARK_REQUEST: