Merge pull request #945 from ThibG/glitch-soc/merge-upstream
Merge upstream changes
This commit is contained in:
		
						commit
						5baae3d9c0
					
				
					 20 changed files with 109 additions and 47 deletions
				
			
		| 
						 | 
					@ -175,7 +175,9 @@ export function submitCompose(routerHistory) {
 | 
				
			||||||
      // To make the app more responsive, immediately get the status into the columns
 | 
					      // To make the app more responsive, immediately get the status into the columns
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const insertIfOnline = (timelineId) => {
 | 
					      const insertIfOnline = (timelineId) => {
 | 
				
			||||||
        if (getState().getIn(['timelines', timelineId, 'items', 0]) !== null) {
 | 
					        const timeline = getState().getIn(['timelines', timelineId]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (timeline && timeline.get('items').size > 0 && timeline.getIn(['items', 0]) !== null && timeline.get('online')) {
 | 
				
			||||||
          dispatch(updateTimeline(timelineId, { ...response.data }));
 | 
					          dispatch(updateTimeline(timelineId, { ...response.data }));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ import {
 | 
				
			||||||
  updateTimeline,
 | 
					  updateTimeline,
 | 
				
			||||||
  deleteFromTimelines,
 | 
					  deleteFromTimelines,
 | 
				
			||||||
  expandHomeTimeline,
 | 
					  expandHomeTimeline,
 | 
				
			||||||
 | 
					  connectTimeline,
 | 
				
			||||||
  disconnectTimeline,
 | 
					  disconnectTimeline,
 | 
				
			||||||
} from './timelines';
 | 
					} from './timelines';
 | 
				
			||||||
import { updateNotifications, expandNotifications } from './notifications';
 | 
					import { updateNotifications, expandNotifications } from './notifications';
 | 
				
			||||||
| 
						 | 
					@ -15,7 +16,12 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return connectStream (path, pollingRefresh, (dispatch, getState) => {
 | 
					  return connectStream (path, pollingRefresh, (dispatch, getState) => {
 | 
				
			||||||
    const locale = getState().getIn(['meta', 'locale']);
 | 
					    const locale = getState().getIn(['meta', 'locale']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
 | 
					      onConnect() {
 | 
				
			||||||
 | 
					        dispatch(connectTimeline(timelineId));
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      onDisconnect() {
 | 
					      onDisconnect() {
 | 
				
			||||||
        dispatch(disconnectTimeline(timelineId));
 | 
					        dispatch(disconnectTimeline(timelineId));
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,7 @@ export const TIMELINE_EXPAND_FAIL    = 'TIMELINE_EXPAND_FAIL';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP';
 | 
					export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const TIMELINE_CONNECT    = 'TIMELINE_CONNECT';
 | 
				
			||||||
export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
 | 
					export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function updateTimeline(timeline, status, accept) {
 | 
					export function updateTimeline(timeline, status, accept) {
 | 
				
			||||||
| 
						 | 
					@ -145,6 +146,13 @@ export function scrollTopTimeline(timeline, top) {
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function connectTimeline(timeline) {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: TIMELINE_CONNECT,
 | 
				
			||||||
 | 
					    timeline,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function disconnectTimeline(timeline) {
 | 
					export function disconnectTimeline(timeline) {
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    type: TIMELINE_DISCONNECT,
 | 
					    type: TIMELINE_DISCONNECT,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -94,7 +94,7 @@ class Poll extends ImmutablePureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  renderOption (option, optionIndex) {
 | 
					  renderOption (option, optionIndex) {
 | 
				
			||||||
    const { poll, disabled } = this.props;
 | 
					    const { poll, disabled } = this.props;
 | 
				
			||||||
    const percent            = (option.get('votes_count') / poll.get('votes_count')) * 100;
 | 
					    const percent            = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100;
 | 
				
			||||||
    const leading            = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count'));
 | 
					    const leading            = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count'));
 | 
				
			||||||
    const active             = !!this.state.selected[`${optionIndex}`];
 | 
					    const active             = !!this.state.selected[`${optionIndex}`];
 | 
				
			||||||
    const showResults        = poll.get('voted') || poll.get('expired');
 | 
					    const showResults        = poll.get('voted') || poll.get('expired');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,7 @@ import {
 | 
				
			||||||
  TIMELINE_EXPAND_REQUEST,
 | 
					  TIMELINE_EXPAND_REQUEST,
 | 
				
			||||||
  TIMELINE_EXPAND_FAIL,
 | 
					  TIMELINE_EXPAND_FAIL,
 | 
				
			||||||
  TIMELINE_SCROLL_TOP,
 | 
					  TIMELINE_SCROLL_TOP,
 | 
				
			||||||
 | 
					  TIMELINE_CONNECT,
 | 
				
			||||||
  TIMELINE_DISCONNECT,
 | 
					  TIMELINE_DISCONNECT,
 | 
				
			||||||
} from 'flavours/glitch/actions/timelines';
 | 
					} from 'flavours/glitch/actions/timelines';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
| 
						 | 
					@ -20,6 +21,7 @@ const initialState = ImmutableMap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const initialTimeline = ImmutableMap({
 | 
					const initialTimeline = ImmutableMap({
 | 
				
			||||||
  unread: 0,
 | 
					  unread: 0,
 | 
				
			||||||
 | 
					  online: false,
 | 
				
			||||||
  top: true,
 | 
					  top: true,
 | 
				
			||||||
  isLoading: false,
 | 
					  isLoading: false,
 | 
				
			||||||
  hasMore: true,
 | 
					  hasMore: true,
 | 
				
			||||||
| 
						 | 
					@ -137,14 +139,13 @@ export default function timelines(state = initialState, action) {
 | 
				
			||||||
    return filterTimeline('home', state, action.relationship, action.statuses);
 | 
					    return filterTimeline('home', state, action.relationship, action.statuses);
 | 
				
			||||||
  case TIMELINE_SCROLL_TOP:
 | 
					  case TIMELINE_SCROLL_TOP:
 | 
				
			||||||
    return updateTop(state, action.timeline, action.top);
 | 
					    return updateTop(state, action.timeline, action.top);
 | 
				
			||||||
 | 
					  case TIMELINE_CONNECT:
 | 
				
			||||||
 | 
					    return state.update(action.timeline, initialTimeline, map => map.set('online', true));
 | 
				
			||||||
  case TIMELINE_DISCONNECT:
 | 
					  case TIMELINE_DISCONNECT:
 | 
				
			||||||
    return state.update(
 | 
					    return state.update(
 | 
				
			||||||
      action.timeline,
 | 
					      action.timeline,
 | 
				
			||||||
      initialTimeline,
 | 
					      initialTimeline,
 | 
				
			||||||
      map => map.update(
 | 
					      map => map.set('online', false).update('items', items => items.first() ? items.unshift(null) : items)
 | 
				
			||||||
        'items',
 | 
					 | 
				
			||||||
        items => items.first() ? items.unshift(null) : items
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  default:
 | 
					  default:
 | 
				
			||||||
    return state;
 | 
					    return state;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,11 +2,11 @@ import WebSocketClient from 'websocket.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const randomIntUpTo = max => Math.floor(Math.random() * Math.floor(max));
 | 
					const randomIntUpTo = max => Math.floor(Math.random() * Math.floor(max));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function connectStream(path, pollingRefresh = null, callbacks = () => ({ onDisconnect() {}, onReceive() {} })) {
 | 
					export function connectStream(path, pollingRefresh = null, callbacks = () => ({ onConnect() {}, onDisconnect() {}, onReceive() {} })) {
 | 
				
			||||||
  return (dispatch, getState) => {
 | 
					  return (dispatch, getState) => {
 | 
				
			||||||
    const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']);
 | 
					    const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']);
 | 
				
			||||||
    const accessToken = getState().getIn(['meta', 'access_token']);
 | 
					    const accessToken = getState().getIn(['meta', 'access_token']);
 | 
				
			||||||
    const { onDisconnect, onReceive } = callbacks(dispatch, getState);
 | 
					    const { onConnect, onDisconnect, onReceive } = callbacks(dispatch, getState);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let polling = null;
 | 
					    let polling = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,6 +28,8 @@ export function connectStream(path, pollingRefresh = null, callbacks = () => ({
 | 
				
			||||||
        if (pollingRefresh) {
 | 
					        if (pollingRefresh) {
 | 
				
			||||||
          clearPolling();
 | 
					          clearPolling();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        onConnect();
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      disconnected () {
 | 
					      disconnected () {
 | 
				
			||||||
| 
						 | 
					@ -47,6 +49,8 @@ export function connectStream(path, pollingRefresh = null, callbacks = () => ({
 | 
				
			||||||
          clearPolling();
 | 
					          clearPolling();
 | 
				
			||||||
          pollingRefresh(dispatch);
 | 
					          pollingRefresh(dispatch);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        onConnect();
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -158,7 +158,9 @@ export function submitCompose(routerHistory) {
 | 
				
			||||||
      // into the columns
 | 
					      // into the columns
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const insertIfOnline = timelineId => {
 | 
					      const insertIfOnline = timelineId => {
 | 
				
			||||||
        if (getState().getIn(['timelines', timelineId, 'items', 0]) !== null) {
 | 
					        const timeline = getState().getIn(['timelines', timelineId]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (timeline && timeline.get('items').size > 0 && timeline.getIn(['items', 0]) !== null && timeline.get('online')) {
 | 
				
			||||||
          dispatch(updateTimeline(timelineId, { ...response.data }));
 | 
					          dispatch(updateTimeline(timelineId, { ...response.data }));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ import {
 | 
				
			||||||
  updateTimeline,
 | 
					  updateTimeline,
 | 
				
			||||||
  deleteFromTimelines,
 | 
					  deleteFromTimelines,
 | 
				
			||||||
  expandHomeTimeline,
 | 
					  expandHomeTimeline,
 | 
				
			||||||
 | 
					  connectTimeline,
 | 
				
			||||||
  disconnectTimeline,
 | 
					  disconnectTimeline,
 | 
				
			||||||
} from './timelines';
 | 
					} from './timelines';
 | 
				
			||||||
import { updateNotifications, expandNotifications } from './notifications';
 | 
					import { updateNotifications, expandNotifications } from './notifications';
 | 
				
			||||||
| 
						 | 
					@ -16,7 +17,12 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return connectStream (path, pollingRefresh, (dispatch, getState) => {
 | 
					  return connectStream (path, pollingRefresh, (dispatch, getState) => {
 | 
				
			||||||
    const locale = getState().getIn(['meta', 'locale']);
 | 
					    const locale = getState().getIn(['meta', 'locale']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
 | 
					      onConnect() {
 | 
				
			||||||
 | 
					        dispatch(connectTimeline(timelineId));
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      onDisconnect() {
 | 
					      onDisconnect() {
 | 
				
			||||||
        dispatch(disconnectTimeline(timelineId));
 | 
					        dispatch(disconnectTimeline(timelineId));
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,7 @@ export const TIMELINE_EXPAND_FAIL    = 'TIMELINE_EXPAND_FAIL';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP';
 | 
					export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const TIMELINE_CONNECT    = 'TIMELINE_CONNECT';
 | 
				
			||||||
export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
 | 
					export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function updateTimeline(timeline, status, accept) {
 | 
					export function updateTimeline(timeline, status, accept) {
 | 
				
			||||||
| 
						 | 
					@ -143,6 +144,13 @@ export function scrollTopTimeline(timeline, top) {
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function connectTimeline(timeline) {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: TIMELINE_CONNECT,
 | 
				
			||||||
 | 
					    timeline,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function disconnectTimeline(timeline) {
 | 
					export function disconnectTimeline(timeline) {
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    type: TIMELINE_DISCONNECT,
 | 
					    type: TIMELINE_DISCONNECT,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -94,7 +94,7 @@ class Poll extends ImmutablePureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  renderOption (option, optionIndex) {
 | 
					  renderOption (option, optionIndex) {
 | 
				
			||||||
    const { poll, disabled } = this.props;
 | 
					    const { poll, disabled } = this.props;
 | 
				
			||||||
    const percent            = (option.get('votes_count') / poll.get('votes_count')) * 100;
 | 
					    const percent            = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100;
 | 
				
			||||||
    const leading            = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count'));
 | 
					    const leading            = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count'));
 | 
				
			||||||
    const active             = !!this.state.selected[`${optionIndex}`];
 | 
					    const active             = !!this.state.selected[`${optionIndex}`];
 | 
				
			||||||
    const showResults        = poll.get('voted') || poll.get('expired');
 | 
					    const showResults        = poll.get('voted') || poll.get('expired');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,7 @@ import {
 | 
				
			||||||
  TIMELINE_EXPAND_REQUEST,
 | 
					  TIMELINE_EXPAND_REQUEST,
 | 
				
			||||||
  TIMELINE_EXPAND_FAIL,
 | 
					  TIMELINE_EXPAND_FAIL,
 | 
				
			||||||
  TIMELINE_SCROLL_TOP,
 | 
					  TIMELINE_SCROLL_TOP,
 | 
				
			||||||
 | 
					  TIMELINE_CONNECT,
 | 
				
			||||||
  TIMELINE_DISCONNECT,
 | 
					  TIMELINE_DISCONNECT,
 | 
				
			||||||
} from '../actions/timelines';
 | 
					} from '../actions/timelines';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
| 
						 | 
					@ -20,6 +21,7 @@ const initialState = ImmutableMap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const initialTimeline = ImmutableMap({
 | 
					const initialTimeline = ImmutableMap({
 | 
				
			||||||
  unread: 0,
 | 
					  unread: 0,
 | 
				
			||||||
 | 
					  online: false,
 | 
				
			||||||
  top: true,
 | 
					  top: true,
 | 
				
			||||||
  isLoading: false,
 | 
					  isLoading: false,
 | 
				
			||||||
  hasMore: true,
 | 
					  hasMore: true,
 | 
				
			||||||
| 
						 | 
					@ -142,14 +144,13 @@ export default function timelines(state = initialState, action) {
 | 
				
			||||||
    return filterTimeline('home', state, action.relationship, action.statuses);
 | 
					    return filterTimeline('home', state, action.relationship, action.statuses);
 | 
				
			||||||
  case TIMELINE_SCROLL_TOP:
 | 
					  case TIMELINE_SCROLL_TOP:
 | 
				
			||||||
    return updateTop(state, action.timeline, action.top);
 | 
					    return updateTop(state, action.timeline, action.top);
 | 
				
			||||||
 | 
					  case TIMELINE_CONNECT:
 | 
				
			||||||
 | 
					    return state.update(action.timeline, initialTimeline, map => map.set('online', true));
 | 
				
			||||||
  case TIMELINE_DISCONNECT:
 | 
					  case TIMELINE_DISCONNECT:
 | 
				
			||||||
    return state.update(
 | 
					    return state.update(
 | 
				
			||||||
      action.timeline,
 | 
					      action.timeline,
 | 
				
			||||||
      initialTimeline,
 | 
					      initialTimeline,
 | 
				
			||||||
      map => map.update(
 | 
					      map => map.set('online', false).update('items', items => items.first() ? items.unshift(null) : items)
 | 
				
			||||||
        'items',
 | 
					 | 
				
			||||||
        items => items.first() ? items.unshift(null) : items
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  default:
 | 
					  default:
 | 
				
			||||||
    return state;
 | 
					    return state;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,11 +2,11 @@ import WebSocketClient from 'websocket.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const randomIntUpTo = max => Math.floor(Math.random() * Math.floor(max));
 | 
					const randomIntUpTo = max => Math.floor(Math.random() * Math.floor(max));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function connectStream(path, pollingRefresh = null, callbacks = () => ({ onDisconnect() {}, onReceive() {} })) {
 | 
					export function connectStream(path, pollingRefresh = null, callbacks = () => ({ onConnect() {}, onDisconnect() {}, onReceive() {} })) {
 | 
				
			||||||
  return (dispatch, getState) => {
 | 
					  return (dispatch, getState) => {
 | 
				
			||||||
    const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']);
 | 
					    const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']);
 | 
				
			||||||
    const accessToken = getState().getIn(['meta', 'access_token']);
 | 
					    const accessToken = getState().getIn(['meta', 'access_token']);
 | 
				
			||||||
    const { onDisconnect, onReceive } = callbacks(dispatch, getState);
 | 
					    const { onConnect, onDisconnect, onReceive } = callbacks(dispatch, getState);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let polling = null;
 | 
					    let polling = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,6 +28,8 @@ export function connectStream(path, pollingRefresh = null, callbacks = () => ({
 | 
				
			||||||
        if (pollingRefresh) {
 | 
					        if (pollingRefresh) {
 | 
				
			||||||
          clearPolling();
 | 
					          clearPolling();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        onConnect();
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      disconnected () {
 | 
					      disconnected () {
 | 
				
			||||||
| 
						 | 
					@ -47,6 +49,8 @@ export function connectStream(path, pollingRefresh = null, callbacks = () => ({
 | 
				
			||||||
          clearPolling();
 | 
					          clearPolling();
 | 
				
			||||||
          pollingRefresh(dispatch);
 | 
					          pollingRefresh(dispatch);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        onConnect();
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -241,6 +241,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def poll_vote?
 | 
					  def poll_vote?
 | 
				
			||||||
    return false if replied_to_status.nil? || replied_to_status.poll.nil? || !replied_to_status.local? || !replied_to_status.poll.options.include?(@object['name'])
 | 
					    return false if replied_to_status.nil? || replied_to_status.poll.nil? || !replied_to_status.local? || !replied_to_status.poll.options.include?(@object['name'])
 | 
				
			||||||
 | 
					    return true if replied_to_status.poll.expired?
 | 
				
			||||||
    replied_to_status.poll.votes.create!(account: @account, choice: replied_to_status.poll.options.index(@object['name']), uri: @object['id'])
 | 
					    replied_to_status.poll.votes.create!(account: @account, choice: replied_to_status.poll.options.index(@object['name']), uri: @object['id'])
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,7 +28,7 @@ class Poll < ApplicationRecord
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  validates :options, presence: true
 | 
					  validates :options, presence: true
 | 
				
			||||||
  validates :expires_at, presence: true, if: :local?
 | 
					  validates :expires_at, presence: true, if: :local?
 | 
				
			||||||
  validates_with PollValidator, if: :local?
 | 
					  validates_with PollValidator, on: :create, if: :local?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  scope :attached, -> { where.not(status_id: nil) }
 | 
					  scope :attached, -> { where.not(status_id: nil) }
 | 
				
			||||||
  scope :unattached, -> { where(status_id: nil) }
 | 
					  scope :unattached, -> { where(status_id: nil) }
 | 
				
			||||||
| 
						 | 
					@ -41,17 +41,17 @@ class Poll < ApplicationRecord
 | 
				
			||||||
  after_commit :reset_parent_cache, on: :update
 | 
					  after_commit :reset_parent_cache, on: :update
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def loaded_options
 | 
					  def loaded_options
 | 
				
			||||||
    options.map.with_index { |title, key| Option.new(self, key.to_s, title, cached_tallies[key]) }
 | 
					    options.map.with_index { |title, key| Option.new(self, key.to_s, title, show_totals_now? ? cached_tallies[key] : nil) }
 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def unloaded_options
 | 
					 | 
				
			||||||
    options.map.with_index { |title, key| Option.new(self, key.to_s, title, nil) }
 | 
					 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def possibly_stale?
 | 
					  def possibly_stale?
 | 
				
			||||||
    remote? && last_fetched_before_expiration? && time_passed_since_last_fetch?
 | 
					    remote? && last_fetched_before_expiration? && time_passed_since_last_fetch?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def voted?(account)
 | 
				
			||||||
 | 
					    account.id == account_id || votes.where(account: account).exists?
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  delegate :local?, to: :account
 | 
					  delegate :local?, to: :account
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def remote?
 | 
					  def remote?
 | 
				
			||||||
| 
						 | 
					@ -95,4 +95,8 @@ class Poll < ApplicationRecord
 | 
				
			||||||
  def time_passed_since_last_fetch?
 | 
					  def time_passed_since_last_fetch?
 | 
				
			||||||
    last_fetched_at.nil? || last_fetched_at < 1.minute.ago
 | 
					    last_fetched_at.nil? || last_fetched_at < 1.minute.ago
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def show_totals_now?
 | 
				
			||||||
 | 
					    expired? || !hide_totals?
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -122,12 +122,8 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def poll_options
 | 
					  def poll_options
 | 
				
			||||||
    if !object.poll.expired? && object.poll.hide_totals?
 | 
					 | 
				
			||||||
      object.poll.unloaded_options
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
    object.poll.loaded_options
 | 
					    object.poll.loaded_options
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def poll_and_multiple?
 | 
					  def poll_and_multiple?
 | 
				
			||||||
    object.poll&.multiple?
 | 
					    object.poll&.multiple?
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@ class REST::PollSerializer < ActiveModel::Serializer
 | 
				
			||||||
  attributes :id, :expires_at, :expired,
 | 
					  attributes :id, :expires_at, :expired,
 | 
				
			||||||
             :multiple, :votes_count
 | 
					             :multiple, :votes_count
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  has_many :dynamic_options, key: :options
 | 
					  has_many :loaded_options, key: :options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  attribute :voted, if: :current_user?
 | 
					  attribute :voted, if: :current_user?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,20 +12,12 @@ class REST::PollSerializer < ActiveModel::Serializer
 | 
				
			||||||
    object.id.to_s
 | 
					    object.id.to_s
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def dynamic_options
 | 
					 | 
				
			||||||
    if !object.expired? && object.hide_totals?
 | 
					 | 
				
			||||||
      object.unloaded_options
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
      object.loaded_options
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def expired
 | 
					  def expired
 | 
				
			||||||
    object.expired?
 | 
					    object.expired?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def voted
 | 
					  def voted
 | 
				
			||||||
    object.votes.where(account: current_user.account).exists?
 | 
					    object.voted?(current_user.account)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def current_user?
 | 
					  def current_user?
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,12 +32,17 @@ class ActivityPub::FetchRemotePollService < BaseService
 | 
				
			||||||
    # votes, so we need to remove them
 | 
					    # votes, so we need to remove them
 | 
				
			||||||
    poll.votes.delete_all if latest_options != poll.options
 | 
					    poll.votes.delete_all if latest_options != poll.options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    begin
 | 
				
			||||||
      poll.update!(
 | 
					      poll.update!(
 | 
				
			||||||
        last_fetched_at: Time.now.utc,
 | 
					        last_fetched_at: Time.now.utc,
 | 
				
			||||||
        expires_at: expires_at,
 | 
					        expires_at: expires_at,
 | 
				
			||||||
        options: latest_options,
 | 
					        options: latest_options,
 | 
				
			||||||
        cached_tallies: items.map { |item| item.dig('replies', 'totalItems') || 0 }
 | 
					        cached_tallies: items.map { |item| item.dig('replies', 'totalItems') || 0 }
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
 | 
					    rescue ActiveRecord::StaleObjectError
 | 
				
			||||||
 | 
					      poll.reload
 | 
				
			||||||
 | 
					      retry
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,8 @@ class VoteService < BaseService
 | 
				
			||||||
    @choices = choices
 | 
					    @choices = choices
 | 
				
			||||||
    @votes   = []
 | 
					    @votes   = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return if @poll.expired?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ApplicationRecord.transaction do
 | 
					    ApplicationRecord.transaction do
 | 
				
			||||||
      @choices.each do |choice|
 | 
					      @choices.each do |choice|
 | 
				
			||||||
        @votes << @poll.votes.create!(account: @account, choice: choice)
 | 
					        @votes << @poll.votes.create!(account: @account, choice: choice)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,8 @@
 | 
				
			||||||
- options      = (!poll.expired? && poll.hide_totals?) ? poll.unloaded_options : poll.loaded_options
 | 
					- show_results = (user_signed_in? && poll.voted?(current_account)) || poll.expired?
 | 
				
			||||||
- voted        = user_signed_in? && poll.votes.where(account: current_account).exists?
 | 
					 | 
				
			||||||
- show_results = voted || poll.expired?
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
.poll
 | 
					.poll
 | 
				
			||||||
  %ul
 | 
					  %ul
 | 
				
			||||||
    - options.each do |option|
 | 
					    - poll.loaded_options.each do |option|
 | 
				
			||||||
      %li
 | 
					      %li
 | 
				
			||||||
        - if show_results
 | 
					        - if show_results
 | 
				
			||||||
          - percent = poll.votes_count > 0 ? 100 * option.votes_count / poll.votes_count : 0
 | 
					          - percent = poll.votes_count > 0 ? 100 * option.votes_count / poll.votes_count : 0
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -482,6 +482,28 @@ RSpec.describe ActivityPub::Activity::Create do
 | 
				
			||||||
          expect(poll.reload.cached_tallies).to eq [1, 0]
 | 
					          expect(poll.reload.cached_tallies).to eq [1, 0]
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      context 'when a vote to an expired local poll' do
 | 
				
			||||||
 | 
					        let(:poll) do
 | 
				
			||||||
 | 
					          poll = Fabricate.build(:poll, options: %w(Yellow Blue), expires_at: 1.day.ago)
 | 
				
			||||||
 | 
					          poll.save(validate: false)
 | 
				
			||||||
 | 
					          poll
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        let!(:local_status) { Fabricate(:status, owned_poll: poll) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let(:object_json) do
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
 | 
				
			||||||
 | 
					            type: 'Note',
 | 
				
			||||||
 | 
					            name: 'Yellow',
 | 
				
			||||||
 | 
					            inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it 'does not add a vote to the poll' do
 | 
				
			||||||
 | 
					          expect(poll.votes.first).to be_nil
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    context 'when sender is followed by local users' do
 | 
					    context 'when sender is followed by local users' do
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue