diff --git a/app/soapbox/actions/settings.js b/app/soapbox/actions/settings.js
index e9b3a0c85..677556dee 100644
--- a/app/soapbox/actions/settings.js
+++ b/app/soapbox/actions/settings.js
@@ -75,7 +75,7 @@ const defaultSettings = ImmutableMap({
community: ImmutableMap({
shows: ImmutableMap({
- reblog: true,
+ reblog: false,
reply: true,
}),
other: ImmutableMap({
diff --git a/app/soapbox/actions/streaming.js b/app/soapbox/actions/streaming.js
index 027491a1c..099cbab69 100644
--- a/app/soapbox/actions/streaming.js
+++ b/app/soapbox/actions/streaming.js
@@ -4,7 +4,7 @@ import {
expandHomeTimeline,
connectTimeline,
disconnectTimeline,
- updateTimelineQueue,
+ processTimelineUpdate,
} from './timelines';
import { updateNotificationsQueue, expandNotifications } from './notifications';
import { updateConversations } from './conversations';
@@ -36,7 +36,7 @@ export function connectTimelineStream(timelineId, path, pollingRefresh = null, a
onReceive(data) {
switch(data.event) {
case 'update':
- dispatch(updateTimelineQueue(timelineId, JSON.parse(data.payload), accept));
+ dispatch(processTimelineUpdate(timelineId, JSON.parse(data.payload), accept));
break;
case 'delete':
dispatch(deleteFromTimelines(data.payload));
diff --git a/app/soapbox/actions/timelines.js b/app/soapbox/actions/timelines.js
index 7ae27b8af..323a545b4 100644
--- a/app/soapbox/actions/timelines.js
+++ b/app/soapbox/actions/timelines.js
@@ -1,6 +1,8 @@
import { importFetchedStatus, importFetchedStatuses } from './importer';
import api, { getLinks } from '../api';
-import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
+import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
+import { getSettings } from 'soapbox/actions/settings';
+import { shouldFilter } from 'soapbox/utils/timelines';
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE';
export const TIMELINE_DELETE = 'TIMELINE_DELETE';
@@ -18,6 +20,19 @@ export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
export const MAX_QUEUED_ITEMS = 40;
+export function processTimelineUpdate(timeline, status, accept) {
+ return (dispatch, getState) => {
+ const columnSettings = getSettings(getState()).get(timeline, ImmutableMap());
+ const shouldSkipQueue = shouldFilter(fromJS(status), columnSettings);
+
+ if (shouldSkipQueue) {
+ return dispatch(updateTimeline(timeline, status, accept));
+ } else {
+ return dispatch(updateTimelineQueue(timeline, status, accept));
+ }
+ };
+}
+
export function updateTimeline(timeline, status, accept) {
return dispatch => {
if (typeof accept === 'function' && !accept(status)) {
diff --git a/app/soapbox/features/notifications/components/column_settings.js b/app/soapbox/features/notifications/components/column_settings.js
index f53ba70c8..a7aa1f87b 100644
--- a/app/soapbox/features/notifications/components/column_settings.js
+++ b/app/soapbox/features/notifications/components/column_settings.js
@@ -58,7 +58,6 @@ export default class ColumnSettings extends React.PureComponent {
-
diff --git a/app/soapbox/features/ui/containers/status_list_container.js b/app/soapbox/features/ui/containers/status_list_container.js
index f54cd2eee..03afa4b88 100644
--- a/app/soapbox/features/ui/containers/status_list_container.js
+++ b/app/soapbox/features/ui/containers/status_list_container.js
@@ -6,6 +6,7 @@ import { debounce } from 'lodash';
import { dequeueTimeline } from 'soapbox/actions/timelines';
import { scrollTopTimeline } from '../../../actions/timelines';
import { getSettings } from 'soapbox/actions/settings';
+import { shouldFilter } from 'soapbox/utils/timelines';
const makeGetStatusIds = () => createSelector([
(state, { type }) => getSettings(state).get(type, ImmutableMap()),
@@ -14,24 +15,9 @@ const makeGetStatusIds = () => createSelector([
(state) => state.get('me'),
], (columnSettings, statusIds, statuses, me) => {
return statusIds.filter(id => {
- if (id === null) return true;
-
- const statusForId = statuses.get(id);
- let showStatus = true;
-
- if (columnSettings.getIn(['shows', 'reblog']) === false) {
- showStatus = showStatus && statusForId.get('reblog') === null;
- }
-
- if (columnSettings.getIn(['shows', 'reply']) === false) {
- showStatus = showStatus && (statusForId.get('in_reply_to_id') === null);
- }
-
- if (columnSettings.getIn(['shows', 'direct']) === false) {
- showStatus = showStatus && (statusForId.get('visibility') !== 'direct');
- }
-
- return showStatus;
+ const status = statuses.get(id);
+ if (!status) return true;
+ return !shouldFilter(status, columnSettings);
});
});
diff --git a/app/soapbox/utils/__tests__/timelines-test.js b/app/soapbox/utils/__tests__/timelines-test.js
new file mode 100644
index 000000000..7b9124559
--- /dev/null
+++ b/app/soapbox/utils/__tests__/timelines-test.js
@@ -0,0 +1,70 @@
+import { shouldFilter } from '../timelines';
+import { fromJS } from 'immutable';
+
+describe('shouldFilter', () => {
+ it('returns false under normal circumstances', () => {
+ const columnSettings = fromJS({});
+ const status = fromJS({});
+ expect(shouldFilter(status, columnSettings)).toBe(false);
+ });
+
+ it('reblog: returns true when `shows.reblog == false`', () => {
+ const columnSettings = fromJS({ shows: { reblog: false } });
+ const status = fromJS({ reblog: {} });
+ expect(shouldFilter(status, columnSettings)).toBe(true);
+ });
+
+ it('reblog: returns false when `shows.reblog == true`', () => {
+ const columnSettings = fromJS({ shows: { reblog: true } });
+ const status = fromJS({ reblog: {} });
+ expect(shouldFilter(status, columnSettings)).toBe(false);
+ });
+
+ it('reply: returns true when `shows.reply == false`', () => {
+ const columnSettings = fromJS({ shows: { reply: false } });
+ const status = fromJS({ in_reply_to_id: '1234' });
+ expect(shouldFilter(status, columnSettings)).toBe(true);
+ });
+
+ it('reply: returns false when `shows.reply == true`', () => {
+ const columnSettings = fromJS({ shows: { reply: true } });
+ const status = fromJS({ in_reply_to_id: '1234' });
+ expect(shouldFilter(status, columnSettings)).toBe(false);
+ });
+
+ it('direct: returns true when `shows.direct == false`', () => {
+ const columnSettings = fromJS({ shows: { direct: false } });
+ const status = fromJS({ visibility: 'direct' });
+ expect(shouldFilter(status, columnSettings)).toBe(true);
+ });
+
+ it('direct: returns false when `shows.direct == true`', () => {
+ const columnSettings = fromJS({ shows: { direct: true } });
+ const status = fromJS({ visibility: 'direct' });
+ expect(shouldFilter(status, columnSettings)).toBe(false);
+ });
+
+ it('direct: returns false for a public post when `shows.direct == false`', () => {
+ const columnSettings = fromJS({ shows: { direct: false } });
+ const status = fromJS({ visibility: 'public' });
+ expect(shouldFilter(status, columnSettings)).toBe(false);
+ });
+
+ it('multiple settings', () => {
+ const columnSettings = fromJS({ shows: { reblog: false, reply: false, direct: false } });
+ const status = fromJS({ reblog: null, in_reply_to_id: null, visibility: 'direct' });
+ expect(shouldFilter(status, columnSettings)).toBe(true);
+ });
+
+ it('multiple settings', () => {
+ const columnSettings = fromJS({ shows: { reblog: false, reply: true, direct: false } });
+ const status = fromJS({ reblog: null, in_reply_to_id: '1234', visibility: 'public' });
+ expect(shouldFilter(status, columnSettings)).toBe(false);
+ });
+
+ it('multiple settings', () => {
+ const columnSettings = fromJS({ shows: { reblog: true, reply: false, direct: true } });
+ const status = fromJS({ reblog: {}, in_reply_to_id: '1234', visibility: 'direct' });
+ expect(shouldFilter(status, columnSettings)).toBe(true);
+ });
+});
diff --git a/app/soapbox/utils/timelines.js b/app/soapbox/utils/timelines.js
new file mode 100644
index 000000000..d15a4fa88
--- /dev/null
+++ b/app/soapbox/utils/timelines.js
@@ -0,0 +1,13 @@
+import { Map as ImmutableMap } from 'immutable';
+
+export const shouldFilter = (status, columnSettings) => {
+ const shows = ImmutableMap({
+ reblog: status.get('reblog') !== null,
+ reply: status.get('in_reply_to_id') !== null,
+ direct: status.get('visibility') === 'direct',
+ });
+
+ return shows.some((value, key) => {
+ return columnSettings.getIn(['shows', key]) === false && value;
+ });
+};
diff --git a/app/styles/overflow_hacks.scss b/app/styles/overflow_hacks.scss
index 204098fca..c00096b72 100644
--- a/app/styles/overflow_hacks.scss
+++ b/app/styles/overflow_hacks.scss
@@ -12,10 +12,30 @@ button.column-header__button.active {
}
.detailed-status__wrapper .detailed-status__action-bar {
- border-radius: 0;
+ border-radius: 0 0 10px 10px;
}
.slist .item-list .column-link {
background-color: transparent;
border-top: 1px solid var(--brand-color--med);
}
+
+.focusable {
+ &:focus {
+ border-radius: 0 0 10px 10px;
+ }
+}
+
+.focusable:focus .status.status-direct {
+ border-radius: 0 0 10px 10px;
+}
+
+.status.status-direct:not(.read) {
+ border-radius: 0 0 10px 10px;
+}
+
+// this still looks like shit but at least it's better than it overflowing
+
+.empty-column-indicator {
+ border-radius: 0 0 10px 10px;
+}