Merge commit 'b6fd14f0e2842eca269ef8962e3c5bd560a76357' into glitch-soc/merge-upstream
Conflicts: - `app/lib/activitypub/parser/status_parser.rb`: Glitch-soc had changes to adjacent lines. Ported upstream's changes.
This commit is contained in:
		
						commit
						51631c785f
					
				| 
						 | 
					@ -1,18 +1,10 @@
 | 
				
			||||||
import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships';
 | 
					import { apiSubmitAccountNote } from 'mastodon/api/accounts';
 | 
				
			||||||
import { createAppAsyncThunk } from 'mastodon/store/typed_functions';
 | 
					import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import api from '../api';
 | 
					export const submitAccountNote = createDataLoadingThunk(
 | 
				
			||||||
 | 
					 | 
				
			||||||
export const submitAccountNote = createAppAsyncThunk(
 | 
					 | 
				
			||||||
  'account_note/submit',
 | 
					  'account_note/submit',
 | 
				
			||||||
  async (args: { id: string; value: string }) => {
 | 
					  ({ accountId, note }: { accountId: string; note: string }) =>
 | 
				
			||||||
    const response = await api().post<ApiRelationshipJSON>(
 | 
					    apiSubmitAccountNote(accountId, note),
 | 
				
			||||||
      `/api/v1/accounts/${args.id}/note`,
 | 
					  (relationship) => ({ relationship }),
 | 
				
			||||||
      {
 | 
					  { skipLoading: true },
 | 
				
			||||||
        comment: args.value,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return { relationship: response.data };
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,10 +3,6 @@ import api, { getLinks } from '../api';
 | 
				
			||||||
import { fetchRelationships } from './accounts';
 | 
					import { fetchRelationships } from './accounts';
 | 
				
			||||||
import { importFetchedAccounts, importFetchedStatus } from './importer';
 | 
					import { importFetchedAccounts, importFetchedStatus } from './importer';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const REBLOG_REQUEST = 'REBLOG_REQUEST';
 | 
					 | 
				
			||||||
export const REBLOG_SUCCESS = 'REBLOG_SUCCESS';
 | 
					 | 
				
			||||||
export const REBLOG_FAIL    = 'REBLOG_FAIL';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const REBLOGS_EXPAND_REQUEST = 'REBLOGS_EXPAND_REQUEST';
 | 
					export const REBLOGS_EXPAND_REQUEST = 'REBLOGS_EXPAND_REQUEST';
 | 
				
			||||||
export const REBLOGS_EXPAND_SUCCESS = 'REBLOGS_EXPAND_SUCCESS';
 | 
					export const REBLOGS_EXPAND_SUCCESS = 'REBLOGS_EXPAND_SUCCESS';
 | 
				
			||||||
export const REBLOGS_EXPAND_FAIL = 'REBLOGS_EXPAND_FAIL';
 | 
					export const REBLOGS_EXPAND_FAIL = 'REBLOGS_EXPAND_FAIL';
 | 
				
			||||||
| 
						 | 
					@ -15,10 +11,6 @@ export const FAVOURITE_REQUEST = 'FAVOURITE_REQUEST';
 | 
				
			||||||
export const FAVOURITE_SUCCESS = 'FAVOURITE_SUCCESS';
 | 
					export const FAVOURITE_SUCCESS = 'FAVOURITE_SUCCESS';
 | 
				
			||||||
export const FAVOURITE_FAIL    = 'FAVOURITE_FAIL';
 | 
					export const FAVOURITE_FAIL    = 'FAVOURITE_FAIL';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const UNREBLOG_REQUEST = 'UNREBLOG_REQUEST';
 | 
					 | 
				
			||||||
export const UNREBLOG_SUCCESS = 'UNREBLOG_SUCCESS';
 | 
					 | 
				
			||||||
export const UNREBLOG_FAIL    = 'UNREBLOG_FAIL';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const UNFAVOURITE_REQUEST = 'UNFAVOURITE_REQUEST';
 | 
					export const UNFAVOURITE_REQUEST = 'UNFAVOURITE_REQUEST';
 | 
				
			||||||
export const UNFAVOURITE_SUCCESS = 'UNFAVOURITE_SUCCESS';
 | 
					export const UNFAVOURITE_SUCCESS = 'UNFAVOURITE_SUCCESS';
 | 
				
			||||||
export const UNFAVOURITE_FAIL    = 'UNFAVOURITE_FAIL';
 | 
					export const UNFAVOURITE_FAIL    = 'UNFAVOURITE_FAIL';
 | 
				
			||||||
| 
						 | 
					@ -51,83 +43,7 @@ export const UNBOOKMARK_REQUEST = 'UNBOOKMARKED_REQUEST';
 | 
				
			||||||
export const UNBOOKMARK_SUCCESS = 'UNBOOKMARKED_SUCCESS';
 | 
					export const UNBOOKMARK_SUCCESS = 'UNBOOKMARKED_SUCCESS';
 | 
				
			||||||
export const UNBOOKMARK_FAIL    = 'UNBOOKMARKED_FAIL';
 | 
					export const UNBOOKMARK_FAIL    = 'UNBOOKMARKED_FAIL';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function reblog(status, visibility) {
 | 
					export * from "./interactions_typed";
 | 
				
			||||||
  return function (dispatch) {
 | 
					 | 
				
			||||||
    dispatch(reblogRequest(status));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    api().post(`/api/v1/statuses/${status.get('id')}/reblog`, { visibility }).then(function (response) {
 | 
					 | 
				
			||||||
      // The reblog API method returns a new status wrapped around the original. In this case we are only
 | 
					 | 
				
			||||||
      // interested in how the original is modified, hence passing it skipping the wrapper
 | 
					 | 
				
			||||||
      dispatch(importFetchedStatus(response.data.reblog));
 | 
					 | 
				
			||||||
      dispatch(reblogSuccess(status));
 | 
					 | 
				
			||||||
    }).catch(function (error) {
 | 
					 | 
				
			||||||
      dispatch(reblogFail(status, error));
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function unreblog(status) {
 | 
					 | 
				
			||||||
  return (dispatch) => {
 | 
					 | 
				
			||||||
    dispatch(unreblogRequest(status));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    api().post(`/api/v1/statuses/${status.get('id')}/unreblog`).then(response => {
 | 
					 | 
				
			||||||
      dispatch(importFetchedStatus(response.data));
 | 
					 | 
				
			||||||
      dispatch(unreblogSuccess(status));
 | 
					 | 
				
			||||||
    }).catch(error => {
 | 
					 | 
				
			||||||
      dispatch(unreblogFail(status, error));
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function reblogRequest(status) {
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    type: REBLOG_REQUEST,
 | 
					 | 
				
			||||||
    status: status,
 | 
					 | 
				
			||||||
    skipLoading: true,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function reblogSuccess(status) {
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    type: REBLOG_SUCCESS,
 | 
					 | 
				
			||||||
    status: status,
 | 
					 | 
				
			||||||
    skipLoading: true,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function reblogFail(status, error) {
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    type: REBLOG_FAIL,
 | 
					 | 
				
			||||||
    status: status,
 | 
					 | 
				
			||||||
    error: error,
 | 
					 | 
				
			||||||
    skipLoading: true,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function unreblogRequest(status) {
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    type: UNREBLOG_REQUEST,
 | 
					 | 
				
			||||||
    status: status,
 | 
					 | 
				
			||||||
    skipLoading: true,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function unreblogSuccess(status) {
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    type: UNREBLOG_SUCCESS,
 | 
					 | 
				
			||||||
    status: status,
 | 
					 | 
				
			||||||
    skipLoading: true,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function unreblogFail(status, error) {
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    type: UNREBLOG_FAIL,
 | 
					 | 
				
			||||||
    status: status,
 | 
					 | 
				
			||||||
    error: error,
 | 
					 | 
				
			||||||
    skipLoading: true,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function favourite(status) {
 | 
					export function favourite(status) {
 | 
				
			||||||
  return function (dispatch) {
 | 
					  return function (dispatch) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,35 @@
 | 
				
			||||||
 | 
					import { apiReblog, apiUnreblog } from 'mastodon/api/interactions';
 | 
				
			||||||
 | 
					import type { StatusVisibility } from 'mastodon/models/status';
 | 
				
			||||||
 | 
					import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { importFetchedStatus } from './importer';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const reblog = createDataLoadingThunk(
 | 
				
			||||||
 | 
					  'status/reblog',
 | 
				
			||||||
 | 
					  ({
 | 
				
			||||||
 | 
					    statusId,
 | 
				
			||||||
 | 
					    visibility,
 | 
				
			||||||
 | 
					  }: {
 | 
				
			||||||
 | 
					    statusId: string;
 | 
				
			||||||
 | 
					    visibility: StatusVisibility;
 | 
				
			||||||
 | 
					  }) => apiReblog(statusId, visibility),
 | 
				
			||||||
 | 
					  (data, { dispatch, discardLoadData }) => {
 | 
				
			||||||
 | 
					    // The reblog API method returns a new status wrapped around the original. In this case we are only
 | 
				
			||||||
 | 
					    // interested in how the original is modified, hence passing it skipping the wrapper
 | 
				
			||||||
 | 
					    dispatch(importFetchedStatus(data.reblog));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // The payload is not used in any actions
 | 
				
			||||||
 | 
					    return discardLoadData;
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const unreblog = createDataLoadingThunk(
 | 
				
			||||||
 | 
					  'status/unreblog',
 | 
				
			||||||
 | 
					  ({ statusId }: { statusId: string }) => apiUnreblog(statusId),
 | 
				
			||||||
 | 
					  (data, { dispatch, discardLoadData }) => {
 | 
				
			||||||
 | 
					    dispatch(importFetchedStatus(data));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // The payload is not used in any actions
 | 
				
			||||||
 | 
					    return discardLoadData;
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
import type { AxiosResponse, RawAxiosRequestHeaders } from 'axios';
 | 
					import type { AxiosResponse, Method, RawAxiosRequestHeaders } from 'axios';
 | 
				
			||||||
import axios from 'axios';
 | 
					import axios from 'axios';
 | 
				
			||||||
import LinkHeader from 'http-link-header';
 | 
					import LinkHeader from 'http-link-header';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -58,3 +58,17 @@ export default function api(withAuthorization = true) {
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function apiRequest<ApiResponse = unknown>(
 | 
				
			||||||
 | 
					  method: Method,
 | 
				
			||||||
 | 
					  url: string,
 | 
				
			||||||
 | 
					  params?: Record<string, unknown>,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					  const { data } = await api().request<ApiResponse>({
 | 
				
			||||||
 | 
					    method,
 | 
				
			||||||
 | 
					    url: '/api/' + url,
 | 
				
			||||||
 | 
					    data: params,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return data;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					import { apiRequest } from 'mastodon/api';
 | 
				
			||||||
 | 
					import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const apiSubmitAccountNote = (id: string, value: string) =>
 | 
				
			||||||
 | 
					  apiRequest<ApiRelationshipJSON>('post', `v1/accounts/${id}/note`, {
 | 
				
			||||||
 | 
					    comment: value,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					import { apiRequest } from 'mastodon/api';
 | 
				
			||||||
 | 
					import type { Status, StatusVisibility } from 'mastodon/models/status';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const apiReblog = (statusId: string, visibility: StatusVisibility) =>
 | 
				
			||||||
 | 
					  apiRequest<{ reblog: Status }>('post', `v1/statuses/${statusId}/reblog`, {
 | 
				
			||||||
 | 
					    visibility,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const apiUnreblog = (statusId: string) =>
 | 
				
			||||||
 | 
					  apiRequest<Status>('post', `v1/statuses/${statusId}/unreblog`);
 | 
				
			||||||
| 
						 | 
					@ -96,9 +96,9 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onModalReblog (status, privacy) {
 | 
					  onModalReblog (status, privacy) {
 | 
				
			||||||
    if (status.get('reblogged')) {
 | 
					    if (status.get('reblogged')) {
 | 
				
			||||||
      dispatch(unreblog(status));
 | 
					      dispatch(unreblog({ statusId: status.get('id') }));
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      dispatch(reblog(status, privacy));
 | 
					      dispatch(reblog({ statusId: status.get('id'), visibility: privacy }));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,7 @@ const mapStateToProps = (state, { account }) => ({
 | 
				
			||||||
const mapDispatchToProps = (dispatch, { account }) => ({
 | 
					const mapDispatchToProps = (dispatch, { account }) => ({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onSave (value) {
 | 
					  onSave (value) {
 | 
				
			||||||
    dispatch(submitAccountNote({ id: account.get('id'), value}));
 | 
					    dispatch(submitAccountNote({ accountId: account.get('id'), note: value }));
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -39,12 +39,12 @@ const mapDispatchToProps = dispatch => ({
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onModalReblog (status, privacy) {
 | 
					  onModalReblog (status, privacy) {
 | 
				
			||||||
    dispatch(reblog(status, privacy));
 | 
					    dispatch(reblog({ statusId: status.get('id'), visibility: privacy }));
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onReblog (status, e) {
 | 
					  onReblog (status, e) {
 | 
				
			||||||
    if (status.get('reblogged')) {
 | 
					    if (status.get('reblogged')) {
 | 
				
			||||||
      dispatch(unreblog(status));
 | 
					      dispatch(unreblog({ statusId: status.get('id') }));
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      if (e.shiftKey || !boostModal) {
 | 
					      if (e.shiftKey || !boostModal) {
 | 
				
			||||||
        this.onModalReblog(status);
 | 
					        this.onModalReblog(status);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -123,7 +123,7 @@ class Footer extends ImmutablePureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _performReblog = (status, privacy) => {
 | 
					  _performReblog = (status, privacy) => {
 | 
				
			||||||
    const { dispatch } = this.props;
 | 
					    const { dispatch } = this.props;
 | 
				
			||||||
    dispatch(reblog(status, privacy));
 | 
					    dispatch(reblog({ statusId: status.get('id'), visibility: privacy }));
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleReblogClick = e => {
 | 
					  handleReblogClick = e => {
 | 
				
			||||||
| 
						 | 
					@ -132,7 +132,7 @@ class Footer extends ImmutablePureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (signedIn) {
 | 
					    if (signedIn) {
 | 
				
			||||||
      if (status.get('reblogged')) {
 | 
					      if (status.get('reblogged')) {
 | 
				
			||||||
        dispatch(unreblog(status));
 | 
					        dispatch(unreblog({ statusId: status.get('id') }));
 | 
				
			||||||
      } else if ((e && e.shiftKey) || !boostModal) {
 | 
					      } else if ((e && e.shiftKey) || !boostModal) {
 | 
				
			||||||
        this._performReblog(status);
 | 
					        this._performReblog(status);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -74,12 +74,12 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onModalReblog (status, privacy) {
 | 
					  onModalReblog (status, privacy) {
 | 
				
			||||||
    dispatch(reblog(status, privacy));
 | 
					    dispatch(reblog({ statusId: status.get('id'), visibility: privacy }));
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onReblog (status, e) {
 | 
					  onReblog (status, e) {
 | 
				
			||||||
    if (status.get('reblogged')) {
 | 
					    if (status.get('reblogged')) {
 | 
				
			||||||
      dispatch(unreblog(status));
 | 
					      dispatch(unreblog({ statusId: status.get('id') }));
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      if (e.shiftKey || !boostModal) {
 | 
					      if (e.shiftKey || !boostModal) {
 | 
				
			||||||
        this.onModalReblog(status);
 | 
					        this.onModalReblog(status);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -299,7 +299,7 @@ class Status extends ImmutablePureComponent {
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleModalReblog = (status, privacy) => {
 | 
					  handleModalReblog = (status, privacy) => {
 | 
				
			||||||
    this.props.dispatch(reblog(status, privacy));
 | 
					    this.props.dispatch(reblog({ statusId: status.get('id'), visibility: privacy }));
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleReblogClick = (status, e) => {
 | 
					  handleReblogClick = (status, e) => {
 | 
				
			||||||
| 
						 | 
					@ -308,7 +308,7 @@ class Status extends ImmutablePureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (signedIn) {
 | 
					    if (signedIn) {
 | 
				
			||||||
      if (status.get('reblogged')) {
 | 
					      if (status.get('reblogged')) {
 | 
				
			||||||
        dispatch(unreblog(status));
 | 
					        dispatch(unreblog({ statusId: status.get('id') }));
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        if ((e && e.shiftKey) || !boostModal) {
 | 
					        if ((e && e.shiftKey) || !boostModal) {
 | 
				
			||||||
          this.handleModalReblog(status);
 | 
					          this.handleModalReblog(status);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,10 +3,6 @@ import { Map as ImmutableMap, fromJS } from 'immutable';
 | 
				
			||||||
import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer';
 | 
					import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer';
 | 
				
			||||||
import { normalizeStatusTranslation } from '../actions/importer/normalizer';
 | 
					import { normalizeStatusTranslation } from '../actions/importer/normalizer';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  REBLOG_REQUEST,
 | 
					 | 
				
			||||||
  REBLOG_FAIL,
 | 
					 | 
				
			||||||
  UNREBLOG_REQUEST,
 | 
					 | 
				
			||||||
  UNREBLOG_FAIL,
 | 
					 | 
				
			||||||
  FAVOURITE_REQUEST,
 | 
					  FAVOURITE_REQUEST,
 | 
				
			||||||
  FAVOURITE_FAIL,
 | 
					  FAVOURITE_FAIL,
 | 
				
			||||||
  UNFAVOURITE_REQUEST,
 | 
					  UNFAVOURITE_REQUEST,
 | 
				
			||||||
| 
						 | 
					@ -16,6 +12,10 @@ import {
 | 
				
			||||||
  UNBOOKMARK_REQUEST,
 | 
					  UNBOOKMARK_REQUEST,
 | 
				
			||||||
  UNBOOKMARK_FAIL,
 | 
					  UNBOOKMARK_FAIL,
 | 
				
			||||||
} from '../actions/interactions';
 | 
					} from '../actions/interactions';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  reblog,
 | 
				
			||||||
 | 
					  unreblog,
 | 
				
			||||||
 | 
					} from '../actions/interactions_typed';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  STATUS_MUTE_SUCCESS,
 | 
					  STATUS_MUTE_SUCCESS,
 | 
				
			||||||
  STATUS_UNMUTE_SUCCESS,
 | 
					  STATUS_UNMUTE_SUCCESS,
 | 
				
			||||||
| 
						 | 
					@ -65,6 +65,7 @@ const statusTranslateUndo = (state, id) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const initialState = ImmutableMap();
 | 
					const initialState = ImmutableMap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** @type {import('@reduxjs/toolkit').Reducer<typeof initialState>} */
 | 
				
			||||||
export default function statuses(state = initialState, action) {
 | 
					export default function statuses(state = initialState, action) {
 | 
				
			||||||
  switch(action.type) {
 | 
					  switch(action.type) {
 | 
				
			||||||
  case STATUS_FETCH_REQUEST:
 | 
					  case STATUS_FETCH_REQUEST:
 | 
				
			||||||
| 
						 | 
					@ -91,14 +92,6 @@ export default function statuses(state = initialState, action) {
 | 
				
			||||||
    return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], false);
 | 
					    return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], false);
 | 
				
			||||||
  case UNBOOKMARK_FAIL:
 | 
					  case UNBOOKMARK_FAIL:
 | 
				
			||||||
    return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], true);
 | 
					    return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], true);
 | 
				
			||||||
  case REBLOG_REQUEST:
 | 
					 | 
				
			||||||
    return state.setIn([action.status.get('id'), 'reblogged'], true);
 | 
					 | 
				
			||||||
  case REBLOG_FAIL:
 | 
					 | 
				
			||||||
    return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'reblogged'], false);
 | 
					 | 
				
			||||||
  case UNREBLOG_REQUEST:
 | 
					 | 
				
			||||||
    return state.setIn([action.status.get('id'), 'reblogged'], false);
 | 
					 | 
				
			||||||
  case UNREBLOG_FAIL:
 | 
					 | 
				
			||||||
    return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'reblogged'], true);
 | 
					 | 
				
			||||||
  case STATUS_MUTE_SUCCESS:
 | 
					  case STATUS_MUTE_SUCCESS:
 | 
				
			||||||
    return state.setIn([action.id, 'muted'], true);
 | 
					    return state.setIn([action.id, 'muted'], true);
 | 
				
			||||||
  case STATUS_UNMUTE_SUCCESS:
 | 
					  case STATUS_UNMUTE_SUCCESS:
 | 
				
			||||||
| 
						 | 
					@ -128,6 +121,15 @@ export default function statuses(state = initialState, action) {
 | 
				
			||||||
  case STATUS_TRANSLATE_UNDO:
 | 
					  case STATUS_TRANSLATE_UNDO:
 | 
				
			||||||
    return statusTranslateUndo(state, action.id);
 | 
					    return statusTranslateUndo(state, action.id);
 | 
				
			||||||
  default:
 | 
					  default:
 | 
				
			||||||
 | 
					    if(reblog.pending.match(action))
 | 
				
			||||||
 | 
					      return state.setIn([action.meta.arg.statusId, 'reblogged'], true);
 | 
				
			||||||
 | 
					    else if(reblog.rejected.match(action))
 | 
				
			||||||
 | 
					      return state.get(action.meta.arg.statusId) === undefined ? state : state.setIn([action.meta.arg.statusId, 'reblogged'], false);
 | 
				
			||||||
 | 
					    else if(unreblog.pending.match(action))
 | 
				
			||||||
 | 
					      return state.setIn([action.meta.arg.statusId, 'reblogged'], false);
 | 
				
			||||||
 | 
					    else if(unreblog.rejected.match(action))
 | 
				
			||||||
 | 
					      return state.get(action.meta.arg.statusId) === undefined ? state : state.setIn([action.meta.arg.statusId, 'reblogged'], true);
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
      return state;
 | 
					      return state;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,8 @@ import { createAsyncThunk } from '@reduxjs/toolkit';
 | 
				
			||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
 | 
					// eslint-disable-next-line @typescript-eslint/no-restricted-imports
 | 
				
			||||||
import { useDispatch, useSelector } from 'react-redux';
 | 
					import { useDispatch, useSelector } from 'react-redux';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type { BaseThunkAPI } from '@reduxjs/toolkit/dist/createAsyncThunk';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import type { AppDispatch, RootState } from './store';
 | 
					import type { AppDispatch, RootState } from './store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
 | 
					export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
 | 
				
			||||||
| 
						 | 
					@ -13,8 +15,192 @@ export interface AsyncThunkRejectValue {
 | 
				
			||||||
  error?: unknown;
 | 
					  error?: unknown;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface AppMeta {
 | 
				
			||||||
 | 
					  skipLoading?: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const createAppAsyncThunk = createAsyncThunk.withTypes<{
 | 
					export const createAppAsyncThunk = createAsyncThunk.withTypes<{
 | 
				
			||||||
  state: RootState;
 | 
					  state: RootState;
 | 
				
			||||||
  dispatch: AppDispatch;
 | 
					  dispatch: AppDispatch;
 | 
				
			||||||
  rejectValue: AsyncThunkRejectValue;
 | 
					  rejectValue: AsyncThunkRejectValue;
 | 
				
			||||||
}>();
 | 
					}>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type AppThunkApi = Pick<
 | 
				
			||||||
 | 
					  BaseThunkAPI<
 | 
				
			||||||
 | 
					    RootState,
 | 
				
			||||||
 | 
					    unknown,
 | 
				
			||||||
 | 
					    AppDispatch,
 | 
				
			||||||
 | 
					    AsyncThunkRejectValue,
 | 
				
			||||||
 | 
					    AppMeta,
 | 
				
			||||||
 | 
					    AppMeta
 | 
				
			||||||
 | 
					  >,
 | 
				
			||||||
 | 
					  'getState' | 'dispatch'
 | 
				
			||||||
 | 
					>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface AppThunkOptions {
 | 
				
			||||||
 | 
					  skipLoading?: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const createBaseAsyncThunk = createAsyncThunk.withTypes<{
 | 
				
			||||||
 | 
					  state: RootState;
 | 
				
			||||||
 | 
					  dispatch: AppDispatch;
 | 
				
			||||||
 | 
					  rejectValue: AsyncThunkRejectValue;
 | 
				
			||||||
 | 
					  fulfilledMeta: AppMeta;
 | 
				
			||||||
 | 
					  rejectedMeta: AppMeta;
 | 
				
			||||||
 | 
					}>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function createThunk<Arg = void, Returned = void>(
 | 
				
			||||||
 | 
					  name: string,
 | 
				
			||||||
 | 
					  creator: (arg: Arg, api: AppThunkApi) => Returned | Promise<Returned>,
 | 
				
			||||||
 | 
					  options: AppThunkOptions = {},
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					  return createBaseAsyncThunk(
 | 
				
			||||||
 | 
					    name,
 | 
				
			||||||
 | 
					    async (
 | 
				
			||||||
 | 
					      arg: Arg,
 | 
				
			||||||
 | 
					      { getState, dispatch, fulfillWithValue, rejectWithValue },
 | 
				
			||||||
 | 
					    ) => {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const result = await creator(arg, { dispatch, getState });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return fulfillWithValue(result, {
 | 
				
			||||||
 | 
					          skipLoading: options.skipLoading,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        return rejectWithValue({ error }, { skipLoading: true });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      getPendingMeta() {
 | 
				
			||||||
 | 
					        if (options.skipLoading) return { skipLoading: true };
 | 
				
			||||||
 | 
					        return {};
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const discardLoadDataInPayload = Symbol('discardLoadDataInPayload');
 | 
				
			||||||
 | 
					type DiscardLoadData = typeof discardLoadDataInPayload;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type OnData<LoadDataResult, ReturnedData> = (
 | 
				
			||||||
 | 
					  data: LoadDataResult,
 | 
				
			||||||
 | 
					  api: AppThunkApi & {
 | 
				
			||||||
 | 
					    discardLoadData: DiscardLoadData;
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					) => ReturnedData | DiscardLoadData | Promise<ReturnedData | DiscardLoadData>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Overload when there is no `onData` method, the payload is the `onData` result
 | 
				
			||||||
 | 
					export function createDataLoadingThunk<
 | 
				
			||||||
 | 
					  LoadDataResult,
 | 
				
			||||||
 | 
					  Args extends Record<string, unknown>,
 | 
				
			||||||
 | 
					>(
 | 
				
			||||||
 | 
					  name: string,
 | 
				
			||||||
 | 
					  loadData: (args: Args) => Promise<LoadDataResult>,
 | 
				
			||||||
 | 
					  thunkOptions?: AppThunkOptions,
 | 
				
			||||||
 | 
					): ReturnType<typeof createThunk<Args, LoadDataResult>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Overload when the `onData` method returns discardLoadDataInPayload, then the payload is empty
 | 
				
			||||||
 | 
					export function createDataLoadingThunk<
 | 
				
			||||||
 | 
					  LoadDataResult,
 | 
				
			||||||
 | 
					  Args extends Record<string, unknown>,
 | 
				
			||||||
 | 
					>(
 | 
				
			||||||
 | 
					  name: string,
 | 
				
			||||||
 | 
					  loadData: (args: Args) => Promise<LoadDataResult>,
 | 
				
			||||||
 | 
					  onDataOrThunkOptions?:
 | 
				
			||||||
 | 
					    | AppThunkOptions
 | 
				
			||||||
 | 
					    | OnData<LoadDataResult, DiscardLoadData>,
 | 
				
			||||||
 | 
					  thunkOptions?: AppThunkOptions,
 | 
				
			||||||
 | 
					): ReturnType<typeof createThunk<Args, void>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Overload when the `onData` method returns nothing, then the mayload is the `onData` result
 | 
				
			||||||
 | 
					export function createDataLoadingThunk<
 | 
				
			||||||
 | 
					  LoadDataResult,
 | 
				
			||||||
 | 
					  Args extends Record<string, unknown>,
 | 
				
			||||||
 | 
					>(
 | 
				
			||||||
 | 
					  name: string,
 | 
				
			||||||
 | 
					  loadData: (args: Args) => Promise<LoadDataResult>,
 | 
				
			||||||
 | 
					  onDataOrThunkOptions?: AppThunkOptions | OnData<LoadDataResult, void>,
 | 
				
			||||||
 | 
					  thunkOptions?: AppThunkOptions,
 | 
				
			||||||
 | 
					): ReturnType<typeof createThunk<Args, LoadDataResult>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Overload when there is an `onData` method returning something
 | 
				
			||||||
 | 
					export function createDataLoadingThunk<
 | 
				
			||||||
 | 
					  LoadDataResult,
 | 
				
			||||||
 | 
					  Args extends Record<string, unknown>,
 | 
				
			||||||
 | 
					  Returned,
 | 
				
			||||||
 | 
					>(
 | 
				
			||||||
 | 
					  name: string,
 | 
				
			||||||
 | 
					  loadData: (args: Args) => Promise<LoadDataResult>,
 | 
				
			||||||
 | 
					  onDataOrThunkOptions?: AppThunkOptions | OnData<LoadDataResult, Returned>,
 | 
				
			||||||
 | 
					  thunkOptions?: AppThunkOptions,
 | 
				
			||||||
 | 
					): ReturnType<typeof createThunk<Args, Returned>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * This function creates a Redux Thunk that handles loading data asynchronously (usually from the API), dispatching `pending`, `fullfilled` and `rejected` actions.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * You can run a callback on the `onData` results to either dispatch side effects or modify the payload.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * It is a wrapper around RTK's [`createAsyncThunk`](https://redux-toolkit.js.org/api/createAsyncThunk)
 | 
				
			||||||
 | 
					 * @param name Prefix for the actions types
 | 
				
			||||||
 | 
					 * @param loadData Function that loads the data. It's (object) argument will become the thunk's argument
 | 
				
			||||||
 | 
					 * @param onDataOrThunkOptions
 | 
				
			||||||
 | 
					 *   Callback called on the results from `loadData`.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *   First argument will be the return from `loadData`.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *   Second argument is an object with: `dispatch`, `getState` and `discardLoadData`.
 | 
				
			||||||
 | 
					 *   It can return:
 | 
				
			||||||
 | 
					 *   - `undefined` (or no explicit return), meaning that the `onData` results will be the payload
 | 
				
			||||||
 | 
					 *   - `discardLoadData` to discard the `onData` results and return an empty payload
 | 
				
			||||||
 | 
					 *   - anything else, which will be the payload
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *   You can also omit this parameter and pass `thunkOptions` directly
 | 
				
			||||||
 | 
					 * @param maybeThunkOptions
 | 
				
			||||||
 | 
					 *   Additional Mastodon specific options for the thunk. Currently supports:
 | 
				
			||||||
 | 
					 *   - `skipLoading` to avoid showing the loading bar when the request is in progress
 | 
				
			||||||
 | 
					 * @returns The created thunk
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function createDataLoadingThunk<
 | 
				
			||||||
 | 
					  LoadDataResult,
 | 
				
			||||||
 | 
					  Args extends Record<string, unknown>,
 | 
				
			||||||
 | 
					  Returned,
 | 
				
			||||||
 | 
					>(
 | 
				
			||||||
 | 
					  name: string,
 | 
				
			||||||
 | 
					  loadData: (args: Args) => Promise<LoadDataResult>,
 | 
				
			||||||
 | 
					  onDataOrThunkOptions?: AppThunkOptions | OnData<LoadDataResult, Returned>,
 | 
				
			||||||
 | 
					  maybeThunkOptions?: AppThunkOptions,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					  let onData: OnData<LoadDataResult, Returned> | undefined;
 | 
				
			||||||
 | 
					  let thunkOptions: AppThunkOptions | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (typeof onDataOrThunkOptions === 'function') onData = onDataOrThunkOptions;
 | 
				
			||||||
 | 
					  else if (typeof onDataOrThunkOptions === 'object')
 | 
				
			||||||
 | 
					    thunkOptions = onDataOrThunkOptions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (maybeThunkOptions) {
 | 
				
			||||||
 | 
					    thunkOptions = maybeThunkOptions;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return createThunk<Args, Returned>(
 | 
				
			||||||
 | 
					    name,
 | 
				
			||||||
 | 
					    async (arg, { getState, dispatch }) => {
 | 
				
			||||||
 | 
					      const data = await loadData(arg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!onData) return data as Returned;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const result = await onData(data, {
 | 
				
			||||||
 | 
					        dispatch,
 | 
				
			||||||
 | 
					        getState,
 | 
				
			||||||
 | 
					        discardLoadData: discardLoadDataInPayload,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // if there is no return in `onData`, we return the `onData` result
 | 
				
			||||||
 | 
					      if (typeof result === 'undefined') return data as Returned;
 | 
				
			||||||
 | 
					      // the user explicitely asked to discard the payload
 | 
				
			||||||
 | 
					      else if (result === discardLoadDataInPayload)
 | 
				
			||||||
 | 
					        return undefined as Returned;
 | 
				
			||||||
 | 
					      else return result;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    thunkOptions,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,8 @@
 | 
				
			||||||
class ActivityPub::Parser::StatusParser
 | 
					class ActivityPub::Parser::StatusParser
 | 
				
			||||||
  include JsonLdHelper
 | 
					  include JsonLdHelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  NORMALIZED_LOCALE_NAMES = LanguagesHelper::SUPPORTED_LOCALES.keys.index_by(&:downcase).freeze
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # @param [Hash] json
 | 
					  # @param [Hash] json
 | 
				
			||||||
  # @param [Hash] options
 | 
					  # @param [Hash] options
 | 
				
			||||||
  # @option options [String] :followers_collection
 | 
					  # @option options [String] :followers_collection
 | 
				
			||||||
| 
						 | 
					@ -89,6 +91,13 @@ class ActivityPub::Parser::StatusParser
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def language
 | 
					  def language
 | 
				
			||||||
 | 
					    lang = raw_language_code
 | 
				
			||||||
 | 
					    lang.presence && NORMALIZED_LOCALE_NAMES.fetch(lang.downcase.to_sym, lang)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def raw_language_code
 | 
				
			||||||
    if content_language_map?
 | 
					    if content_language_map?
 | 
				
			||||||
      @object['contentMap'].keys.first
 | 
					      @object['contentMap'].keys.first
 | 
				
			||||||
    elsif name_language_map?
 | 
					    elsif name_language_map?
 | 
				
			||||||
| 
						 | 
					@ -102,8 +111,6 @@ class ActivityPub::Parser::StatusParser
 | 
				
			||||||
    @object['directMessage']
 | 
					    @object['directMessage']
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def audience_to
 | 
					  def audience_to
 | 
				
			||||||
    as_array(@object['to'] || @json['to']).map { |x| value_or_id(x) }
 | 
					    as_array(@object['to'] || @json['to']).map { |x| value_or_id(x) }
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,50 @@
 | 
				
			||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require 'rails_helper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RSpec.describe ActivityPub::Parser::StatusParser do
 | 
				
			||||||
 | 
					  subject { described_class.new(json) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers', domain: 'example.com', uri: 'https://example.com/actor') }
 | 
				
			||||||
 | 
					  let(:follower) { Fabricate(:account, username: 'bob') }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let(:json) do
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      '@context': 'https://www.w3.org/ns/activitystreams',
 | 
				
			||||||
 | 
					      id: [ActivityPub::TagManager.instance.uri_for(sender), '#foo'].join,
 | 
				
			||||||
 | 
					      type: 'Create',
 | 
				
			||||||
 | 
					      actor: ActivityPub::TagManager.instance.uri_for(sender),
 | 
				
			||||||
 | 
					      object: object_json,
 | 
				
			||||||
 | 
					    }.with_indifferent_access
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let(:object_json) do
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      id: [ActivityPub::TagManager.instance.uri_for(sender), 'post1'].join('/'),
 | 
				
			||||||
 | 
					      type: 'Note',
 | 
				
			||||||
 | 
					      to: [
 | 
				
			||||||
 | 
					        'https://www.w3.org/ns/activitystreams#Public',
 | 
				
			||||||
 | 
					        ActivityPub::TagManager.instance.uri_for(follower),
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      content: '@bob lorem ipsum',
 | 
				
			||||||
 | 
					      contentMap: {
 | 
				
			||||||
 | 
					        EN: '@bob lorem ipsum',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      published: 1.hour.ago.utc.iso8601,
 | 
				
			||||||
 | 
					      updated: 1.hour.ago.utc.iso8601,
 | 
				
			||||||
 | 
					      tag: {
 | 
				
			||||||
 | 
					        type: 'Mention',
 | 
				
			||||||
 | 
					        href: ActivityPub::TagManager.instance.uri_for(follower),
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it 'correctly parses status' do
 | 
				
			||||||
 | 
					    expect(subject).to have_attributes(
 | 
				
			||||||
 | 
					      text: '@bob lorem ipsum',
 | 
				
			||||||
 | 
					      uri: [ActivityPub::TagManager.instance.uri_for(sender), 'post1'].join('/'),
 | 
				
			||||||
 | 
					      reply: false,
 | 
				
			||||||
 | 
					      language: :en
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
		Loading…
	
		Reference in New Issue