mirror of
https://git.kescher.at/CatCatNya/catstodon.git
synced 2024-11-22 11:48:06 +01:00
Merge branch 'refs/heads/glitch-soc' into develop
# Conflicts: # app/javascript/flavours/glitch/actions/interactions.js # app/javascript/flavours/glitch/reducers/statuses.js
This commit is contained in:
commit
6d448d46ff
138 changed files with 1578 additions and 1087 deletions
2
Gemfile
2
Gemfile
|
@ -103,6 +103,8 @@ gem 'rdf-normalize', '~> 0.5'
|
|||
|
||||
gem 'private_address_check', '~> 0.5'
|
||||
|
||||
gem 'opentelemetry-api', '~> 1.2.5'
|
||||
|
||||
group :opentelemetry do
|
||||
gem 'opentelemetry-exporter-otlp', '~> 0.26.3', require: false
|
||||
gem 'opentelemetry-instrumentation-active_job', '~> 0.7.1', require: false
|
||||
|
|
|
@ -732,7 +732,7 @@ GEM
|
|||
rspec-mocks (~> 3.0)
|
||||
sidekiq (>= 5, < 8)
|
||||
rspec-support (3.13.1)
|
||||
rubocop (1.63.5)
|
||||
rubocop (1.64.0)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
parallel (~> 1.10)
|
||||
|
@ -883,7 +883,7 @@ GEM
|
|||
webfinger (1.2.0)
|
||||
activesupport
|
||||
httpclient (>= 2.4)
|
||||
webmock (3.23.0)
|
||||
webmock (3.23.1)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
|
@ -981,6 +981,7 @@ DEPENDENCIES
|
|||
omniauth-rails_csrf_protection (~> 1.0)
|
||||
omniauth-saml (~> 2.0)
|
||||
omniauth_openid_connect (~> 0.6.1)
|
||||
opentelemetry-api (~> 1.2.5)
|
||||
opentelemetry-exporter-otlp (~> 0.26.3)
|
||||
opentelemetry-instrumentation-active_job (~> 0.7.1)
|
||||
opentelemetry-instrumentation-active_model_serializers (~> 0.20.1)
|
||||
|
|
|
@ -44,7 +44,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
end
|
||||
|
||||
def build_resource(hash = nil)
|
||||
super(hash)
|
||||
super
|
||||
|
||||
resource.locale = I18n.locale
|
||||
resource.invite_code = @invite&.code if resource.invite_code.blank?
|
||||
|
|
|
@ -1,18 +1,10 @@
|
|||
import type { ApiRelationshipJSON } from 'flavours/glitch/api_types/relationships';
|
||||
import { createAppAsyncThunk } from 'flavours/glitch/store/typed_functions';
|
||||
import { apiSubmitAccountNote } from 'flavours/glitch/api/accounts';
|
||||
import { createDataLoadingThunk } from 'flavours/glitch/store/typed_functions';
|
||||
|
||||
import api from '../api';
|
||||
|
||||
export const submitAccountNote = createAppAsyncThunk(
|
||||
export const submitAccountNote = createDataLoadingThunk(
|
||||
'account_note/submit',
|
||||
async (args: { id: string; value: string }) => {
|
||||
const response = await api().post<ApiRelationshipJSON>(
|
||||
`/api/v1/accounts/${args.id}/note`,
|
||||
{
|
||||
comment: args.value,
|
||||
},
|
||||
);
|
||||
|
||||
return { relationship: response.data };
|
||||
},
|
||||
({ accountId, note }: { accountId: string; note: string }) =>
|
||||
apiSubmitAccountNote(accountId, note),
|
||||
(relationship) => ({ relationship }),
|
||||
{ skipLoading: true },
|
||||
);
|
||||
|
|
|
@ -3,10 +3,6 @@ import api, { getLinks } from '../api';
|
|||
import { fetchRelationships } from './accounts';
|
||||
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_SUCCESS = 'REBLOGS_EXPAND_SUCCESS';
|
||||
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_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_SUCCESS = 'UNFAVOURITE_SUCCESS';
|
||||
export const UNFAVOURITE_FAIL = 'UNFAVOURITE_FAIL';
|
||||
|
@ -61,83 +53,7 @@ export const REACTION_REMOVE_REQUEST = 'REACTION_REMOVE_REQUEST';
|
|||
export const REACTION_REMOVE_SUCCESS = 'REACTION_REMOVE_SUCCESS';
|
||||
export const REACTION_REMOVE_FAIL = 'REACTION_REMOVE_FAIL';
|
||||
|
||||
export function reblog(status, visibility) {
|
||||
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 * from "./interactions_typed";
|
||||
|
||||
export function favourite(status) {
|
||||
return function (dispatch) {
|
||||
|
|
35
app/javascript/flavours/glitch/actions/interactions_typed.ts
Normal file
35
app/javascript/flavours/glitch/actions/interactions_typed.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { apiReblog, apiUnreblog } from 'flavours/glitch/api/interactions';
|
||||
import type { StatusVisibility } from 'flavours/glitch/models/status';
|
||||
import { createDataLoadingThunk } from 'flavours/glitch/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 LinkHeader from 'http-link-header';
|
||||
|
||||
|
@ -40,11 +40,11 @@ const authorizationTokenFromInitialState = (): RawAxiosRequestHeaders => {
|
|||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function api() {
|
||||
export default function api(withAuthorization = true) {
|
||||
return axios.create({
|
||||
headers: {
|
||||
...csrfHeader,
|
||||
...authorizationTokenFromInitialState(),
|
||||
...(withAuthorization ? authorizationTokenFromInitialState() : {}),
|
||||
},
|
||||
|
||||
transformResponse: [
|
||||
|
@ -58,3 +58,17 @@ export default function api() {
|
|||
],
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
7
app/javascript/flavours/glitch/api/accounts.ts
Normal file
7
app/javascript/flavours/glitch/api/accounts.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { apiRequest } from 'flavours/glitch/api';
|
||||
import type { ApiRelationshipJSON } from 'flavours/glitch/api_types/relationships';
|
||||
|
||||
export const apiSubmitAccountNote = (id: string, value: string) =>
|
||||
apiRequest<ApiRelationshipJSON>('post', `v1/accounts/${id}/note`, {
|
||||
comment: value,
|
||||
});
|
10
app/javascript/flavours/glitch/api/interactions.ts
Normal file
10
app/javascript/flavours/glitch/api/interactions.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { apiRequest } from 'flavours/glitch/api';
|
||||
import type { Status, StatusVisibility } from 'flavours/glitch/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`);
|
|
@ -48,7 +48,7 @@ export default class Counter extends PureComponent {
|
|||
componentDidMount () {
|
||||
const { measure, start_at, end_at, params } = this.props;
|
||||
|
||||
api().post('/api/v1/admin/measures', { keys: [measure], start_at, end_at, [measure]: params }).then(res => {
|
||||
api(false).post('/api/v1/admin/measures', { keys: [measure], start_at, end_at, [measure]: params }).then(res => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
data: res.data,
|
||||
|
|
|
@ -26,7 +26,7 @@ export default class Dimension extends PureComponent {
|
|||
componentDidMount () {
|
||||
const { start_at, end_at, dimension, limit, params } = this.props;
|
||||
|
||||
api().post('/api/v1/admin/dimensions', { keys: [dimension], start_at, end_at, limit, [dimension]: params }).then(res => {
|
||||
api(false).post('/api/v1/admin/dimensions', { keys: [dimension], start_at, end_at, limit, [dimension]: params }).then(res => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
data: res.data,
|
||||
|
|
|
@ -27,7 +27,7 @@ export default class ImpactReport extends PureComponent {
|
|||
include_subdomains: true,
|
||||
};
|
||||
|
||||
api().post('/api/v1/admin/measures', {
|
||||
api(false).post('/api/v1/admin/measures', {
|
||||
keys: ['instance_accounts', 'instance_follows', 'instance_followers'],
|
||||
start_at: null,
|
||||
end_at: null,
|
||||
|
|
|
@ -105,7 +105,7 @@ class ReportReasonSelector extends PureComponent {
|
|||
};
|
||||
|
||||
componentDidMount() {
|
||||
api().get('/api/v1/instance').then(res => {
|
||||
api(false).get('/api/v1/instance').then(res => {
|
||||
this.setState({
|
||||
rules: res.data.rules,
|
||||
});
|
||||
|
@ -122,7 +122,7 @@ class ReportReasonSelector extends PureComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
api().put(`/api/v1/admin/reports/${id}`, {
|
||||
api(false).put(`/api/v1/admin/reports/${id}`, {
|
||||
category,
|
||||
rule_ids: category === 'violation' ? rule_ids : [],
|
||||
}).catch(err => {
|
||||
|
|
|
@ -34,7 +34,7 @@ export default class Retention extends PureComponent {
|
|||
componentDidMount () {
|
||||
const { start_at, end_at, frequency } = this.props;
|
||||
|
||||
api().post('/api/v1/admin/retention', { start_at, end_at, frequency }).then(res => {
|
||||
api(false).post('/api/v1/admin/retention', { start_at, end_at, frequency }).then(res => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
data: res.data,
|
||||
|
|
|
@ -22,7 +22,7 @@ export default class Trends extends PureComponent {
|
|||
componentDidMount () {
|
||||
const { limit } = this.props;
|
||||
|
||||
api().get('/api/v1/admin/trends/tags', { params: { limit } }).then(res => {
|
||||
api(false).get('/api/v1/admin/trends/tags', { params: { limit } }).then(res => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
data: res.data,
|
||||
|
|
|
@ -117,9 +117,9 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
|
|||
|
||||
onModalReblog (status, privacy) {
|
||||
if (status.get('reblogged')) {
|
||||
dispatch(unreblog(status));
|
||||
dispatch(unreblog({ statusId: status.get('id') }));
|
||||
} 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 }) => ({
|
||||
|
||||
onSave (value) {
|
||||
dispatch(submitAccountNote({ id: account.get('id'), value}));
|
||||
dispatch(submitAccountNote({ accountId: account.get('id'), note: value }));
|
||||
},
|
||||
|
||||
});
|
||||
|
|
|
@ -15,7 +15,7 @@ const mapStateToProps = (state, { columnId }) => {
|
|||
return {
|
||||
settings: columns.get(index).get('params'),
|
||||
onLoad (value) {
|
||||
return api(() => state).get('/api/v2/search', { params: { q: value, type: 'hashtags' } }).then(response => {
|
||||
return api().get('/api/v2/search', { params: { q: value, type: 'hashtags' } }).then(response => {
|
||||
return (response.data.hashtags || []).map((tag) => {
|
||||
return { value: tag.name, label: `#${tag.name}` };
|
||||
});
|
||||
|
|
|
@ -36,12 +36,12 @@ const mapDispatchToProps = dispatch => ({
|
|||
},
|
||||
|
||||
onModalReblog (status, privacy) {
|
||||
dispatch(reblog(status, privacy));
|
||||
dispatch(reblog({ statusId: status.get('id'), visibility: privacy }));
|
||||
},
|
||||
|
||||
onReblog (status, e) {
|
||||
if (status.get('reblogged')) {
|
||||
dispatch(unreblog(status));
|
||||
dispatch(unreblog({ statusId: status.get('id') }));
|
||||
} else {
|
||||
if (e.shiftKey || !boostModal) {
|
||||
this.onModalReblog(status);
|
||||
|
|
|
@ -125,7 +125,7 @@ class Footer extends ImmutablePureComponent {
|
|||
|
||||
_performReblog = (status, privacy) => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(reblog(status, privacy));
|
||||
dispatch(reblog({ statusId: status.get('id'), visibility: privacy }));
|
||||
};
|
||||
|
||||
handleReblogClick = e => {
|
||||
|
@ -134,7 +134,7 @@ class Footer extends ImmutablePureComponent {
|
|||
|
||||
if (signedIn) {
|
||||
if (status.get('reblogged')) {
|
||||
dispatch(unreblog(status));
|
||||
dispatch(unreblog({ statusId: status.get('id') }));
|
||||
} else if ((e && e.shiftKey) || !boostModal) {
|
||||
this._performReblog(status);
|
||||
} else {
|
||||
|
|
|
@ -71,12 +71,12 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
},
|
||||
|
||||
onModalReblog (status, privacy) {
|
||||
dispatch(reblog(status, privacy));
|
||||
dispatch(reblog({ statusId: status.get('id'), visibility: privacy }));
|
||||
},
|
||||
|
||||
onReblog (status, e) {
|
||||
if (status.get('reblogged')) {
|
||||
dispatch(unreblog(status));
|
||||
dispatch(unreblog({ statusId: status.get('id') }));
|
||||
} else {
|
||||
if (e.shiftKey || !boostModal) {
|
||||
this.onModalReblog(status);
|
||||
|
|
|
@ -361,9 +361,9 @@ class Status extends ImmutablePureComponent {
|
|||
const { dispatch } = this.props;
|
||||
|
||||
if (status.get('reblogged')) {
|
||||
dispatch(unreblog(status));
|
||||
dispatch(unreblog({ statusId: status.get('id') }));
|
||||
} else {
|
||||
dispatch(reblog(status, privacy));
|
||||
dispatch(reblog({ statusId: status.get('id'), visibility: privacy }));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -3,10 +3,6 @@ import { Map as ImmutableMap, fromJS } from 'immutable';
|
|||
import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer';
|
||||
import { normalizeStatusTranslation } from '../actions/importer/normalizer';
|
||||
import {
|
||||
REBLOG_REQUEST,
|
||||
REBLOG_FAIL,
|
||||
UNREBLOG_REQUEST,
|
||||
UNREBLOG_FAIL,
|
||||
FAVOURITE_REQUEST,
|
||||
FAVOURITE_FAIL,
|
||||
UNFAVOURITE_REQUEST,
|
||||
|
@ -21,6 +17,10 @@ import {
|
|||
REACTION_ADD_REQUEST,
|
||||
REACTION_REMOVE_REQUEST,
|
||||
} from '../actions/interactions';
|
||||
import {
|
||||
reblog,
|
||||
unreblog,
|
||||
} from '../actions/interactions_typed';
|
||||
import {
|
||||
STATUS_MUTE_SUCCESS,
|
||||
STATUS_UNMUTE_SUCCESS,
|
||||
|
@ -107,6 +107,7 @@ const statusTranslateUndo = (state, id) => {
|
|||
|
||||
const initialState = ImmutableMap();
|
||||
|
||||
/** @type {import('@reduxjs/toolkit').Reducer<typeof initialState>} */
|
||||
export default function statuses(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case STATUS_FETCH_REQUEST:
|
||||
|
@ -133,10 +134,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);
|
||||
case UNBOOKMARK_FAIL:
|
||||
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 REACTION_UPDATE:
|
||||
return updateReactionCount(state, action.reaction);
|
||||
case REACTION_ADD_REQUEST:
|
||||
|
@ -145,10 +142,6 @@ export default function statuses(state = initialState, action) {
|
|||
case REACTION_REMOVE_REQUEST:
|
||||
case REACTION_ADD_FAIL:
|
||||
return removeReaction(state, action.id, action.name);
|
||||
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:
|
||||
return state.setIn([action.id, 'muted'], true);
|
||||
case STATUS_UNMUTE_SUCCESS:
|
||||
|
@ -178,6 +171,15 @@ export default function statuses(state = initialState, action) {
|
|||
case STATUS_TRANSLATE_UNDO:
|
||||
return statusTranslateUndo(state, action.id);
|
||||
default:
|
||||
return state;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ import { createAsyncThunk } from '@reduxjs/toolkit';
|
|||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import type { BaseThunkAPI } from '@reduxjs/toolkit/dist/createAsyncThunk';
|
||||
|
||||
import type { AppDispatch, RootState } from './store';
|
||||
|
||||
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
|
||||
|
@ -13,8 +15,192 @@ export interface AsyncThunkRejectValue {
|
|||
error?: unknown;
|
||||
}
|
||||
|
||||
interface AppMeta {
|
||||
skipLoading?: boolean;
|
||||
}
|
||||
|
||||
export const createAppAsyncThunk = createAsyncThunk.withTypes<{
|
||||
state: RootState;
|
||||
dispatch: AppDispatch;
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,18 +1,10 @@
|
|||
import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships';
|
||||
import { createAppAsyncThunk } from 'mastodon/store/typed_functions';
|
||||
import { apiSubmitAccountNote } from 'mastodon/api/accounts';
|
||||
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
|
||||
|
||||
import api from '../api';
|
||||
|
||||
export const submitAccountNote = createAppAsyncThunk(
|
||||
export const submitAccountNote = createDataLoadingThunk(
|
||||
'account_note/submit',
|
||||
async (args: { id: string; value: string }) => {
|
||||
const response = await api().post<ApiRelationshipJSON>(
|
||||
`/api/v1/accounts/${args.id}/note`,
|
||||
{
|
||||
comment: args.value,
|
||||
},
|
||||
);
|
||||
|
||||
return { relationship: response.data };
|
||||
},
|
||||
({ accountId, note }: { accountId: string; note: string }) =>
|
||||
apiSubmitAccountNote(accountId, note),
|
||||
(relationship) => ({ relationship }),
|
||||
{ skipLoading: true },
|
||||
);
|
||||
|
|
|
@ -3,10 +3,6 @@ import api, { getLinks } from '../api';
|
|||
import { fetchRelationships } from './accounts';
|
||||
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_SUCCESS = 'REBLOGS_EXPAND_SUCCESS';
|
||||
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_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_SUCCESS = 'UNFAVOURITE_SUCCESS';
|
||||
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_FAIL = 'UNBOOKMARKED_FAIL';
|
||||
|
||||
export function reblog(status, visibility) {
|
||||
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 * from "./interactions_typed";
|
||||
|
||||
export function favourite(status) {
|
||||
return function (dispatch) {
|
||||
|
|
35
app/javascript/mastodon/actions/interactions_typed.ts
Normal file
35
app/javascript/mastodon/actions/interactions_typed.ts
Normal file
|
@ -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 LinkHeader from 'http-link-header';
|
||||
|
||||
|
@ -40,11 +40,11 @@ const authorizationTokenFromInitialState = (): RawAxiosRequestHeaders => {
|
|||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function api() {
|
||||
export default function api(withAuthorization = true) {
|
||||
return axios.create({
|
||||
headers: {
|
||||
...csrfHeader,
|
||||
...authorizationTokenFromInitialState(),
|
||||
...(withAuthorization ? authorizationTokenFromInitialState() : {}),
|
||||
},
|
||||
|
||||
transformResponse: [
|
||||
|
@ -58,3 +58,17 @@ export default function api() {
|
|||
],
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
7
app/javascript/mastodon/api/accounts.ts
Normal file
7
app/javascript/mastodon/api/accounts.ts
Normal file
|
@ -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,
|
||||
});
|
10
app/javascript/mastodon/api/interactions.ts
Normal file
10
app/javascript/mastodon/api/interactions.ts
Normal file
|
@ -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`);
|
|
@ -48,7 +48,7 @@ export default class Counter extends PureComponent {
|
|||
componentDidMount () {
|
||||
const { measure, start_at, end_at, params } = this.props;
|
||||
|
||||
api().post('/api/v1/admin/measures', { keys: [measure], start_at, end_at, [measure]: params }).then(res => {
|
||||
api(false).post('/api/v1/admin/measures', { keys: [measure], start_at, end_at, [measure]: params }).then(res => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
data: res.data,
|
||||
|
|
|
@ -26,7 +26,7 @@ export default class Dimension extends PureComponent {
|
|||
componentDidMount () {
|
||||
const { start_at, end_at, dimension, limit, params } = this.props;
|
||||
|
||||
api().post('/api/v1/admin/dimensions', { keys: [dimension], start_at, end_at, limit, [dimension]: params }).then(res => {
|
||||
api(false).post('/api/v1/admin/dimensions', { keys: [dimension], start_at, end_at, limit, [dimension]: params }).then(res => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
data: res.data,
|
||||
|
|
|
@ -27,7 +27,7 @@ export default class ImpactReport extends PureComponent {
|
|||
include_subdomains: true,
|
||||
};
|
||||
|
||||
api().post('/api/v1/admin/measures', {
|
||||
api(false).post('/api/v1/admin/measures', {
|
||||
keys: ['instance_accounts', 'instance_follows', 'instance_followers'],
|
||||
start_at: null,
|
||||
end_at: null,
|
||||
|
|
|
@ -105,7 +105,7 @@ class ReportReasonSelector extends PureComponent {
|
|||
};
|
||||
|
||||
componentDidMount() {
|
||||
api().get('/api/v1/instance').then(res => {
|
||||
api(false).get('/api/v1/instance').then(res => {
|
||||
this.setState({
|
||||
rules: res.data.rules,
|
||||
});
|
||||
|
@ -122,7 +122,7 @@ class ReportReasonSelector extends PureComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
api().put(`/api/v1/admin/reports/${id}`, {
|
||||
api(false).put(`/api/v1/admin/reports/${id}`, {
|
||||
category,
|
||||
rule_ids: category === 'violation' ? rule_ids : [],
|
||||
}).catch(err => {
|
||||
|
|
|
@ -34,7 +34,7 @@ export default class Retention extends PureComponent {
|
|||
componentDidMount () {
|
||||
const { start_at, end_at, frequency } = this.props;
|
||||
|
||||
api().post('/api/v1/admin/retention', { start_at, end_at, frequency }).then(res => {
|
||||
api(false).post('/api/v1/admin/retention', { start_at, end_at, frequency }).then(res => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
data: res.data,
|
||||
|
|
|
@ -22,7 +22,7 @@ export default class Trends extends PureComponent {
|
|||
componentDidMount () {
|
||||
const { limit } = this.props;
|
||||
|
||||
api().get('/api/v1/admin/trends/tags', { params: { limit } }).then(res => {
|
||||
api(false).get('/api/v1/admin/trends/tags', { params: { limit } }).then(res => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
data: res.data,
|
||||
|
|
|
@ -96,9 +96,9 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
|
|||
|
||||
onModalReblog (status, privacy) {
|
||||
if (status.get('reblogged')) {
|
||||
dispatch(unreblog(status));
|
||||
dispatch(unreblog({ statusId: status.get('id') }));
|
||||
} 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 }) => ({
|
||||
|
||||
onSave (value) {
|
||||
dispatch(submitAccountNote({ id: account.get('id'), value}));
|
||||
dispatch(submitAccountNote({ accountId: account.get('id'), note: value }));
|
||||
},
|
||||
|
||||
});
|
||||
|
|
|
@ -15,7 +15,7 @@ const mapStateToProps = (state, { columnId }) => {
|
|||
return {
|
||||
settings: columns.get(index).get('params'),
|
||||
onLoad (value) {
|
||||
return api(() => state).get('/api/v2/search', { params: { q: value, type: 'hashtags' } }).then(response => {
|
||||
return api().get('/api/v2/search', { params: { q: value, type: 'hashtags' } }).then(response => {
|
||||
return (response.data.hashtags || []).map((tag) => {
|
||||
return { value: tag.name, label: `#${tag.name}` };
|
||||
});
|
||||
|
|
|
@ -39,12 +39,12 @@ const mapDispatchToProps = dispatch => ({
|
|||
},
|
||||
|
||||
onModalReblog (status, privacy) {
|
||||
dispatch(reblog(status, privacy));
|
||||
dispatch(reblog({ statusId: status.get('id'), visibility: privacy }));
|
||||
},
|
||||
|
||||
onReblog (status, e) {
|
||||
if (status.get('reblogged')) {
|
||||
dispatch(unreblog(status));
|
||||
dispatch(unreblog({ statusId: status.get('id') }));
|
||||
} else {
|
||||
if (e.shiftKey || !boostModal) {
|
||||
this.onModalReblog(status);
|
||||
|
|
|
@ -123,7 +123,7 @@ class Footer extends ImmutablePureComponent {
|
|||
|
||||
_performReblog = (status, privacy) => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(reblog(status, privacy));
|
||||
dispatch(reblog({ statusId: status.get('id'), visibility: privacy }));
|
||||
};
|
||||
|
||||
handleReblogClick = e => {
|
||||
|
@ -132,7 +132,7 @@ class Footer extends ImmutablePureComponent {
|
|||
|
||||
if (signedIn) {
|
||||
if (status.get('reblogged')) {
|
||||
dispatch(unreblog(status));
|
||||
dispatch(unreblog({ statusId: status.get('id') }));
|
||||
} else if ((e && e.shiftKey) || !boostModal) {
|
||||
this._performReblog(status);
|
||||
} else {
|
||||
|
|
|
@ -74,12 +74,12 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
},
|
||||
|
||||
onModalReblog (status, privacy) {
|
||||
dispatch(reblog(status, privacy));
|
||||
dispatch(reblog({ statusId: status.get('id'), visibility: privacy }));
|
||||
},
|
||||
|
||||
onReblog (status, e) {
|
||||
if (status.get('reblogged')) {
|
||||
dispatch(unreblog(status));
|
||||
dispatch(unreblog({ statusId: status.get('id') }));
|
||||
} else {
|
||||
if (e.shiftKey || !boostModal) {
|
||||
this.onModalReblog(status);
|
||||
|
|
|
@ -299,7 +299,7 @@ class Status extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleModalReblog = (status, privacy) => {
|
||||
this.props.dispatch(reblog(status, privacy));
|
||||
this.props.dispatch(reblog({ statusId: status.get('id'), visibility: privacy }));
|
||||
};
|
||||
|
||||
handleReblogClick = (status, e) => {
|
||||
|
@ -308,7 +308,7 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
if (signedIn) {
|
||||
if (status.get('reblogged')) {
|
||||
dispatch(unreblog(status));
|
||||
dispatch(unreblog({ statusId: status.get('id') }));
|
||||
} else {
|
||||
if ((e && e.shiftKey) || !boostModal) {
|
||||
this.handleModalReblog(status);
|
||||
|
|
|
@ -469,6 +469,7 @@
|
|||
"notification.follow": "{name} падпісаўся на вас",
|
||||
"notification.follow_request": "{name} адправіў запыт на падпіску",
|
||||
"notification.mention": "{name} згадаў вас",
|
||||
"notification.moderation-warning.learn_more": "Даведацца больш",
|
||||
"notification.own_poll": "Ваша апытанне скончылася",
|
||||
"notification.poll": "Апытанне, дзе вы прынялі ўдзел, скончылася",
|
||||
"notification.reblog": "{name} пашырыў ваш допіс",
|
||||
|
|
|
@ -234,7 +234,7 @@
|
|||
"embed.preview": "이렇게 표시됩니다:",
|
||||
"emoji_button.activity": "활동",
|
||||
"emoji_button.clear": "지우기",
|
||||
"emoji_button.custom": "사용자 지정",
|
||||
"emoji_button.custom": "커스텀",
|
||||
"emoji_button.flags": "깃발",
|
||||
"emoji_button.food": "음식과 마실것",
|
||||
"emoji_button.label": "에모지 추가",
|
||||
|
|
|
@ -491,7 +491,7 @@
|
|||
"onboarding.actions.go_to_home": "Dodieties uz manu mājas plūsmu",
|
||||
"onboarding.compose.template": "Sveiki, #Mastodon!",
|
||||
"onboarding.follows.empty": "Diemžēl pašlaik nevar parādīt rezultātus. Vari mēģināt izmantot meklēšanu vai pārlūkot izpētes lapu, lai atrastu cilvēkus, kuriem sekot, vai vēlāk mēģināt vēlreiz.",
|
||||
"onboarding.follows.lead": "Tava mājas plūsma ir galvenais veids, kā izbaudīt Mastodon. Jo vairāk cilvēku sekosi, jo aktīvāk un interesantāk tas būs. Lai sāktu, šeit ir daži ieteikumi:",
|
||||
"onboarding.follows.lead": "Tava mājas plūsma ir galvenais veids, kā pieredzēt Mastodon. Jo vairāk cilvēkiem sekosi, jo dzīvīgāka un aizraujošāka tā būs. Lai sāktu, šeit ir daži ieteikumi:",
|
||||
"onboarding.follows.title": "Pielāgo savu mājas barotni",
|
||||
"onboarding.profile.discoverable": "Padarīt manu profilu atklājamu",
|
||||
"onboarding.profile.display_name": "Attēlojamais vārds",
|
||||
|
|
|
@ -297,6 +297,7 @@
|
|||
"filter_modal.select_filter.subtitle": "Bruk ein eksisterande kategori eller opprett ein ny",
|
||||
"filter_modal.select_filter.title": "Filtrer dette innlegget",
|
||||
"filter_modal.title.status": "Filtrer eit innlegg",
|
||||
"filtered_notifications_banner.mentions": "{count, plural, one {omtale} other {omtaler}}",
|
||||
"filtered_notifications_banner.pending_requests": "Varsel frå {count, plural, =0 {ingen} one {ein person} other {# folk}} du kanskje kjenner",
|
||||
"filtered_notifications_banner.title": "Filtrerte varslingar",
|
||||
"firehose.all": "Alle",
|
||||
|
@ -307,6 +308,8 @@
|
|||
"follow_requests.unlocked_explanation": "Sjølv om kontoen din ikkje er låst tenkte dei som driv {domain} at du kanskje ville gå gjennom førespurnadar frå desse kontoane manuelt.",
|
||||
"follow_suggestions.curated_suggestion": "Utvalt av staben",
|
||||
"follow_suggestions.dismiss": "Ikkje vis igjen",
|
||||
"follow_suggestions.featured_longer": "Hanplukka av gjengen på {domain}",
|
||||
"follow_suggestions.friends_of_friends_longer": "Populært hjå dei du fylgjer",
|
||||
"follow_suggestions.hints.featured": "Denne profilen er handplukka av folka på {domain}.",
|
||||
"follow_suggestions.hints.friends_of_friends": "Denne profilen er populær hjå dei du fylgjer.",
|
||||
"follow_suggestions.hints.most_followed": "Mange på {domain} fylgjer denne profilen.",
|
||||
|
@ -314,6 +317,8 @@
|
|||
"follow_suggestions.hints.similar_to_recently_followed": "Denne profilen liknar på dei andre profilane du har fylgt i det siste.",
|
||||
"follow_suggestions.personalized_suggestion": "Personleg forslag",
|
||||
"follow_suggestions.popular_suggestion": "Populært forslag",
|
||||
"follow_suggestions.popular_suggestion_longer": "Populært på {domain}",
|
||||
"follow_suggestions.similar_to_recently_followed_longer": "Liknar på profilar du har fylgt i det siste",
|
||||
"follow_suggestions.view_all": "Vis alle",
|
||||
"follow_suggestions.who_to_follow": "Kven du kan fylgja",
|
||||
"followed_tags": "Fylgde emneknaggar",
|
||||
|
|
|
@ -3,10 +3,6 @@ import { Map as ImmutableMap, fromJS } from 'immutable';
|
|||
import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer';
|
||||
import { normalizeStatusTranslation } from '../actions/importer/normalizer';
|
||||
import {
|
||||
REBLOG_REQUEST,
|
||||
REBLOG_FAIL,
|
||||
UNREBLOG_REQUEST,
|
||||
UNREBLOG_FAIL,
|
||||
FAVOURITE_REQUEST,
|
||||
FAVOURITE_FAIL,
|
||||
UNFAVOURITE_REQUEST,
|
||||
|
@ -16,6 +12,10 @@ import {
|
|||
UNBOOKMARK_REQUEST,
|
||||
UNBOOKMARK_FAIL,
|
||||
} from '../actions/interactions';
|
||||
import {
|
||||
reblog,
|
||||
unreblog,
|
||||
} from '../actions/interactions_typed';
|
||||
import {
|
||||
STATUS_MUTE_SUCCESS,
|
||||
STATUS_UNMUTE_SUCCESS,
|
||||
|
@ -65,6 +65,7 @@ const statusTranslateUndo = (state, id) => {
|
|||
|
||||
const initialState = ImmutableMap();
|
||||
|
||||
/** @type {import('@reduxjs/toolkit').Reducer<typeof initialState>} */
|
||||
export default function statuses(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
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);
|
||||
case UNBOOKMARK_FAIL:
|
||||
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:
|
||||
return state.setIn([action.id, 'muted'], true);
|
||||
case STATUS_UNMUTE_SUCCESS:
|
||||
|
@ -128,6 +121,15 @@ export default function statuses(state = initialState, action) {
|
|||
case STATUS_TRANSLATE_UNDO:
|
||||
return statusTranslateUndo(state, action.id);
|
||||
default:
|
||||
return state;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ import { createAsyncThunk } from '@reduxjs/toolkit';
|
|||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import type { BaseThunkAPI } from '@reduxjs/toolkit/dist/createAsyncThunk';
|
||||
|
||||
import type { AppDispatch, RootState } from './store';
|
||||
|
||||
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
|
||||
|
@ -13,8 +15,192 @@ export interface AsyncThunkRejectValue {
|
|||
error?: unknown;
|
||||
}
|
||||
|
||||
interface AppMeta {
|
||||
skipLoading?: boolean;
|
||||
}
|
||||
|
||||
export const createAppAsyncThunk = createAsyncThunk.withTypes<{
|
||||
state: RootState;
|
||||
dispatch: AppDispatch;
|
||||
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
|
||||
include JsonLdHelper
|
||||
|
||||
NORMALIZED_LOCALE_NAMES = LanguagesHelper::SUPPORTED_LOCALES.keys.index_by(&:downcase).freeze
|
||||
|
||||
# @param [Hash] json
|
||||
# @param [Hash] options
|
||||
# @option options [String] :followers_collection
|
||||
|
@ -89,6 +91,13 @@ class ActivityPub::Parser::StatusParser
|
|||
end
|
||||
|
||||
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?
|
||||
@object['contentMap'].keys.first
|
||||
elsif name_language_map?
|
||||
|
@ -102,8 +111,6 @@ class ActivityPub::Parser::StatusParser
|
|||
@object['directMessage']
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def audience_to
|
||||
as_array(@object['to'] || @json['to']).map { |x| value_or_id(x) }
|
||||
end
|
||||
|
|
|
@ -33,6 +33,6 @@ class ActivityPub::Serializer < ActiveModel::Serializer
|
|||
adapter_options[:named_contexts].merge!(_named_contexts)
|
||||
adapter_options[:context_extensions].merge!(_context_extensions)
|
||||
end
|
||||
super(adapter_options, options, adapter_instance)
|
||||
super
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,7 +31,7 @@ class AdvancedTextFormatter < TextFormatter
|
|||
# @option options [String] :content_type
|
||||
def initialize(text, options = {})
|
||||
@content_type = options.delete(:content_type)
|
||||
super(text, options)
|
||||
super
|
||||
|
||||
@text = format_markdown(text) if content_type == 'text/markdown'
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ require_relative 'shared_timed_stack'
|
|||
|
||||
class ConnectionPool::SharedConnectionPool < ConnectionPool
|
||||
def initialize(options = {}, &block)
|
||||
super(options, &block)
|
||||
super
|
||||
|
||||
@available = ConnectionPool::SharedTimedStack.new(@size, &block)
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
class RSS::Channel < RSS::Element
|
||||
def initialize
|
||||
super()
|
||||
super
|
||||
|
||||
@root = create_element('channel')
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
class RSS::Item < RSS::Element
|
||||
def initialize
|
||||
super()
|
||||
super
|
||||
|
||||
@root = create_element('item')
|
||||
end
|
||||
|
|
|
@ -23,7 +23,7 @@ module Attachmentable
|
|||
|
||||
included do
|
||||
def self.has_attached_file(name, options = {}) # rubocop:disable Naming/PredicateName
|
||||
super(name, options)
|
||||
super
|
||||
|
||||
send(:"before_#{name}_validate", prepend: true) do
|
||||
attachment = send(name)
|
||||
|
|
|
@ -151,13 +151,23 @@ class AccountSearchService < BaseService
|
|||
end
|
||||
|
||||
def call(query, account = nil, options = {})
|
||||
@query = query&.strip&.gsub(/\A@/, '')
|
||||
@limit = options[:limit].to_i
|
||||
@offset = options[:offset].to_i
|
||||
@options = options
|
||||
@account = account
|
||||
MastodonOTELTracer.in_span('AccountSearchService#call') do |span|
|
||||
@query = query&.strip&.gsub(/\A@/, '')
|
||||
@limit = options[:limit].to_i
|
||||
@offset = options[:offset].to_i
|
||||
@options = options
|
||||
@account = account
|
||||
|
||||
search_service_results.compact.uniq
|
||||
span.add_attributes(
|
||||
'search.offset' => @offset,
|
||||
'search.limit' => @limit,
|
||||
'search.backend' => Chewy.enabled? ? 'elasticsearch' : 'database'
|
||||
)
|
||||
|
||||
search_service_results.compact.uniq.tap do |results|
|
||||
span.set_attribute('search.results.count', results.size)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -2,14 +2,24 @@
|
|||
|
||||
class StatusesSearchService < BaseService
|
||||
def call(query, account = nil, options = {})
|
||||
@query = query&.strip
|
||||
@account = account
|
||||
@options = options
|
||||
@limit = options[:limit].to_i
|
||||
@offset = options[:offset].to_i
|
||||
MastodonOTELTracer.in_span('StatusesSearchService#call') do |span|
|
||||
@query = query&.strip
|
||||
@account = account
|
||||
@options = options
|
||||
@limit = options[:limit].to_i
|
||||
@offset = options[:offset].to_i
|
||||
convert_deprecated_options!
|
||||
|
||||
convert_deprecated_options!
|
||||
status_search_results
|
||||
span.add_attributes(
|
||||
'search.offset' => @offset,
|
||||
'search.limit' => @limit,
|
||||
'search.backend' => Chewy.enabled? ? 'elasticsearch' : 'database'
|
||||
)
|
||||
|
||||
status_search_results.tap do |results|
|
||||
span.set_attribute('search.results.count', results.size)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -2,15 +2,25 @@
|
|||
|
||||
class TagSearchService < BaseService
|
||||
def call(query, options = {})
|
||||
@query = query.strip.delete_prefix('#')
|
||||
@offset = options.delete(:offset).to_i
|
||||
@limit = options.delete(:limit).to_i
|
||||
@options = options
|
||||
MastodonOTELTracer.in_span('TagSearchService#call') do |span|
|
||||
@query = query.strip.delete_prefix('#')
|
||||
@offset = options.delete(:offset).to_i
|
||||
@limit = options.delete(:limit).to_i
|
||||
@options = options
|
||||
|
||||
results = from_elasticsearch if Chewy.enabled?
|
||||
results ||= from_database
|
||||
span.add_attributes(
|
||||
'search.offset' => @offset,
|
||||
'search.limit' => @limit,
|
||||
'search.backend' => Chewy.enabled? ? 'elasticsearch' : 'database'
|
||||
)
|
||||
|
||||
results
|
||||
results = from_elasticsearch if Chewy.enabled?
|
||||
results ||= from_database
|
||||
|
||||
span.set_attribute('search.results.count', results.size)
|
||||
|
||||
results
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -38,7 +38,7 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
super(direction, migrations, schema_migration, internal_metadata, target_version)
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -66,3 +66,5 @@ if ENV.keys.any? { |name| name.match?(/OTEL_.*_ENDPOINT/) }
|
|||
c.service_version = Mastodon::Version.to_s
|
||||
end
|
||||
end
|
||||
|
||||
MastodonOTELTracer = OpenTelemetry.tracer_provider.tracer('mastodon')
|
||||
|
|
|
@ -852,7 +852,6 @@ an:
|
|||
delete: Borrar
|
||||
edit_preset: Editar aviso predeterminau
|
||||
empty: Encara no has definiu garra preajuste d'alvertencia.
|
||||
title: Editar configuración predeterminada d'avisos
|
||||
webhooks:
|
||||
add_new: Anyadir endpoint
|
||||
delete: Eliminar
|
||||
|
|
|
@ -1013,7 +1013,6 @@ ar:
|
|||
delete: حذف
|
||||
edit_preset: تعديل نموذج التحذير
|
||||
empty: لم تحدد أي إعدادات تحذير مسبقة بعد.
|
||||
title: إدارة نماذج التحذير
|
||||
webhooks:
|
||||
add_new: إضافة نقطة نهاية
|
||||
delete: حذف
|
||||
|
|
|
@ -400,8 +400,6 @@ ast:
|
|||
usable: Pue usase
|
||||
title: Tendencies
|
||||
trending: En tendencia
|
||||
warning_presets:
|
||||
title: Xestión d'alvertencies preconfiguraes
|
||||
webhooks:
|
||||
add_new: Amestar un estremu
|
||||
delete: Desaniciar
|
||||
|
|
|
@ -983,7 +983,6 @@ be:
|
|||
delete: Выдаліць
|
||||
edit_preset: Рэдагаваць шаблон папярэджання
|
||||
empty: Вы яшчэ не вызначылі ніякіх шаблонаў папярэджанняў.
|
||||
title: Кіраванне шаблонамі папярэджанняў
|
||||
webhooks:
|
||||
add_new: Дадаць канцавую кропку
|
||||
delete: Выдаліць
|
||||
|
|
|
@ -951,7 +951,7 @@ bg:
|
|||
delete: Изтриване
|
||||
edit_preset: Редакция на предварителните настройки
|
||||
empty: Все още няма предварителни настройки за предупрежденията.
|
||||
title: Управление на предварителните настройки
|
||||
title: Предупредителни образци
|
||||
webhooks:
|
||||
add_new: Добавяне на крайна точка
|
||||
delete: Изтриване
|
||||
|
|
|
@ -951,7 +951,7 @@ ca:
|
|||
delete: Elimina
|
||||
edit_preset: Edita l'avís predeterminat
|
||||
empty: Encara no has definit cap preavís.
|
||||
title: Gestiona les configuracions predefinides dels avisos
|
||||
title: Predefinicions d'avís
|
||||
webhooks:
|
||||
add_new: Afegir extrem
|
||||
delete: Elimina
|
||||
|
|
|
@ -548,7 +548,6 @@ ckb:
|
|||
add_new: زیادکردنی نوێ
|
||||
delete: سڕینەوە
|
||||
edit_preset: دەستکاریکردنی ئاگاداری پێشگریمان
|
||||
title: بەڕێوەبردنی ئاگادارکردنەوە پێشسازدان
|
||||
admin_mailer:
|
||||
new_pending_account:
|
||||
body: وردەکاریهەژمارە نوێیەکە لە خوارەوەیە. دەتوانیت ئەم نەرمەکالا پەسەند بکەیت یان ڕەت بکەیتەوە.
|
||||
|
|
|
@ -510,7 +510,6 @@ co:
|
|||
add_new: Aghjunghje
|
||||
delete: Sguassà
|
||||
edit_preset: Cambià a preselezzione d'avertimentu
|
||||
title: Amministrà e preselezzione d'avertimentu
|
||||
admin_mailer:
|
||||
new_pending_account:
|
||||
body: I ditagli di u novu contu sò quì sottu. Pudete appruvà o righjittà a dumanda.
|
||||
|
|
|
@ -984,7 +984,6 @@ cs:
|
|||
delete: Smazat
|
||||
edit_preset: Upravit předlohu pro varování
|
||||
empty: Zatím jste nedefinovali žádné předlohy varování.
|
||||
title: Spravovat předlohy pro varování
|
||||
webhooks:
|
||||
add_new: Přidat koncový bod
|
||||
delete: Smazat
|
||||
|
|
|
@ -297,6 +297,7 @@ cy:
|
|||
update_custom_emoji_html: Mae %{name} wedi diweddaru emoji %{target}
|
||||
update_domain_block_html: Mae %{name} wedi diweddaru bloc parth %{target}
|
||||
update_ip_block_html: Mae %{name} wedi newid rheol IP %{target}
|
||||
update_report_html: Mae %{name} wedi diweddaru adroddiad %{target}
|
||||
update_status_html: Mae %{name} wedi diweddaru postiad gan %{target}
|
||||
update_user_role_html: Mae %{name} wedi newid rôl %{target}
|
||||
deleted_account: cyfrif wedi'i ddileu
|
||||
|
@ -1018,7 +1019,7 @@ cy:
|
|||
delete: Dileu
|
||||
edit_preset: Golygu rhagosodiad rhybudd
|
||||
empty: Nid ydych wedi diffinio unrhyw ragosodiadau rhybudd eto.
|
||||
title: Rheoli rhagosodiadau rhybudd
|
||||
title: Rhagosodiadau rhybuddion
|
||||
webhooks:
|
||||
add_new: Ychwanegu diweddbwynt
|
||||
delete: Dileu
|
||||
|
|
|
@ -951,7 +951,7 @@ da:
|
|||
delete: Slet
|
||||
edit_preset: Redigér advarselsforvalg
|
||||
empty: Ingen advarselsforvalg defineret endnu.
|
||||
title: Håndtérr advarselsforvalg
|
||||
title: Præindstillinger for advarsel
|
||||
webhooks:
|
||||
add_new: Tilføj endepunkt
|
||||
delete: Slet
|
||||
|
|
|
@ -951,7 +951,7 @@ de:
|
|||
delete: Löschen
|
||||
edit_preset: Warnvorlage bearbeiten
|
||||
empty: Du hast noch keine Warnvorlagen hinzugefügt.
|
||||
title: Warnvorlagen verwalten
|
||||
title: Warnvorlagen
|
||||
webhooks:
|
||||
add_new: Endpunkt hinzufügen
|
||||
delete: Löschen
|
||||
|
|
|
@ -22,7 +22,7 @@ lt:
|
|||
action: Patvirtinti el. pašto adresą
|
||||
action_with_app: Patvirtinti ir grįžti į %{app}
|
||||
explanation: Šiuo el. pašto adresu sukūrei paskyrą %{host}. Iki jos aktyvavimo liko vienas paspaudimas. Jei tai buvo ne tu, ignoruok šį el. laišką.
|
||||
explanation_when_pending: Šiuo el. pašto adresu pateikei paraišką pakvietimui į %{host}. Kai patvirtinsi savo el. pašto adresą, mes peržiūrėsime tavo paraišką. Gali prisijungti ir pakeisti savo duomenis arba ištrinti paskyrą, tačiau negalėsi naudotis daugeliu funkcijų, kol tavo paskyra nebus patvirtinta. Jei tavo paraiška bus atmesta, duomenys bus pašalinti, todėl jokių papildomų veiksmų iš tavęs nereikės. Jei tai buvo ne tu, ignoruok šį el. laišką.
|
||||
explanation_when_pending: Šiuo el. pašto adresu pateikei paraišką pakvietimui į %{host}. Kai patvirtinsi savo el. pašto adresą, mes peržiūrėsime tavo paraišką. Gali prisijungti ir pakeisti savo duomenis arba ištrinti paskyrą, bet negalėsi naudotis daugeliu funkcijų, kol tavo paskyra nebus patvirtinta. Jei tavo paraiška bus atmesta, duomenys bus pašalinti, todėl jokių papildomų veiksmų iš tavęs nereikės. Jei tai buvo ne tu, ignoruok šį el. laišką.
|
||||
extra_html: Taip pat peržiūrėk <a href="%{terms_path}">serverio taisykles</a> ir <a href="%{policy_path}">mūsų paslaugų teikimo sąlygas</a>.
|
||||
subject: 'Mastodon: patvirtinimo instrukcijos %{instance}'
|
||||
title: Patvirtinti el. pašto adresą
|
||||
|
|
|
@ -903,7 +903,6 @@ el:
|
|||
delete: Διαγραφή
|
||||
edit_preset: Ενημέρωση προκαθορισμένης προειδοποίησης
|
||||
empty: Δεν έχετε ακόμη ορίσει κάποια προκαθορισμένη προειδοποίηση.
|
||||
title: Διαχείριση προκαθορισμένων προειδοποιήσεων
|
||||
webhooks:
|
||||
add_new: Προσθήκη σημείου τερματισμού
|
||||
delete: Διαγραφή
|
||||
|
|
|
@ -950,7 +950,6 @@ en-GB:
|
|||
delete: Delete
|
||||
edit_preset: Edit warning preset
|
||||
empty: You haven't defined any warning presets yet.
|
||||
title: Warning presets
|
||||
webhooks:
|
||||
add_new: Add endpoint
|
||||
delete: Delete
|
||||
|
|
|
@ -919,7 +919,6 @@ eo:
|
|||
delete: Forigi
|
||||
edit_preset: Redakti avertan antaŭagordon
|
||||
empty: Vi ankoraŭ ne difinis iun ajn antaŭagordon de averto.
|
||||
title: Administri avertajn antaŭagordojn
|
||||
webhooks:
|
||||
add_new: Aldoni finpunkton
|
||||
delete: Forigi
|
||||
|
|
|
@ -951,7 +951,7 @@ es-AR:
|
|||
delete: Eliminar
|
||||
edit_preset: Editar preajuste de advertencia
|
||||
empty: Aún no ha definido ningún preajuste de advertencia.
|
||||
title: Administrar preajustes de advertencia
|
||||
title: Preajustes de advertencia
|
||||
webhooks:
|
||||
add_new: Agregar punto final
|
||||
delete: Eliminar
|
||||
|
|
|
@ -951,7 +951,7 @@ es-MX:
|
|||
delete: Borrar
|
||||
edit_preset: Editar aviso predeterminado
|
||||
empty: Aún no has definido ningún preajuste de advertencia.
|
||||
title: Editar configuración predeterminada de avisos
|
||||
title: Preajustes de advertencia
|
||||
webhooks:
|
||||
add_new: Añadir endpoint
|
||||
delete: Eliminar
|
||||
|
|
|
@ -951,7 +951,7 @@ es:
|
|||
delete: Borrar
|
||||
edit_preset: Editar aviso predeterminado
|
||||
empty: Aún no has definido ningún preajuste de advertencia.
|
||||
title: Editar configuración predeterminada de avisos
|
||||
title: Preajustes de advertencia
|
||||
webhooks:
|
||||
add_new: Añadir endpoint
|
||||
delete: Eliminar
|
||||
|
|
|
@ -949,7 +949,6 @@ et:
|
|||
delete: Kustuta
|
||||
edit_preset: Hoiatuse eelseadistuse muutmine
|
||||
empty: Hoiatuste eelseadeid pole defineeritud.
|
||||
title: Halda hoiatuste eelseadistusi
|
||||
webhooks:
|
||||
add_new: Lisa lõpp-punkt
|
||||
delete: Kustuta
|
||||
|
|
|
@ -952,7 +952,6 @@ eu:
|
|||
delete: Ezabatu
|
||||
edit_preset: Editatu abisu aurre-ezarpena
|
||||
empty: Ez duzu abisu aurrezarpenik definitu oraindik.
|
||||
title: Kudeatu abisu aurre-ezarpenak
|
||||
webhooks:
|
||||
add_new: Gehitu amaiera-puntua
|
||||
delete: Ezabatu
|
||||
|
|
|
@ -808,7 +808,6 @@ fa:
|
|||
delete: زدودن
|
||||
edit_preset: ویرایش هشدار پیشفرض
|
||||
empty: هنز هیچ پیشتنظیم هشداری را تعریف نکردهاید.
|
||||
title: مدیریت هشدارهای پیشفرض
|
||||
webhooks:
|
||||
add_new: افزودن نقطهٔ پایانی
|
||||
delete: حذف
|
||||
|
|
|
@ -951,7 +951,7 @@ fi:
|
|||
delete: Poista
|
||||
edit_preset: Muokkaa varoituksen esiasetusta
|
||||
empty: Et ole vielä määrittänyt yhtäkään varoitusten esiasetusta.
|
||||
title: Hallitse varoitusten esiasetuksia
|
||||
title: Varoituksen esiasetukset
|
||||
webhooks:
|
||||
add_new: Lisää päätepiste
|
||||
delete: Poista
|
||||
|
|
|
@ -951,7 +951,7 @@ fo:
|
|||
delete: Strika
|
||||
edit_preset: Rætta ávaringar-undanstilling
|
||||
empty: Tú hevur ikki ásett nakrar ávaringar-undanstillingar enn.
|
||||
title: Stýr ávaringar-undanstillingar
|
||||
title: Undanstillingar fyri ávaring
|
||||
webhooks:
|
||||
add_new: Legg endapunkt afturat
|
||||
delete: Strika
|
||||
|
|
|
@ -949,7 +949,6 @@ fr-CA:
|
|||
delete: Supprimer
|
||||
edit_preset: Éditer les avertissements prédéfinis
|
||||
empty: Vous n'avez pas encore créé de paramètres prédéfinis pour les avertissements.
|
||||
title: Gérer les avertissements prédéfinis
|
||||
webhooks:
|
||||
add_new: Ajouter un point de terminaison
|
||||
delete: Supprimer
|
||||
|
|
|
@ -949,7 +949,6 @@ fr:
|
|||
delete: Supprimer
|
||||
edit_preset: Éditer les avertissements prédéfinis
|
||||
empty: Vous n'avez pas encore créé de paramètres prédéfinis pour les avertissements.
|
||||
title: Gérer les avertissements prédéfinis
|
||||
webhooks:
|
||||
add_new: Ajouter un point de terminaison
|
||||
delete: Supprimer
|
||||
|
|
|
@ -949,7 +949,6 @@ fy:
|
|||
delete: Fuortsmite
|
||||
edit_preset: Foarynstelling foar warskôging bewurkje
|
||||
empty: Jo hawwe noch gjin foarynstellingen foar warskôgingen tafoege.
|
||||
title: Foarynstellingen foar warskôgingen beheare
|
||||
webhooks:
|
||||
add_new: Einpunt tafoegje
|
||||
delete: Fuortsmite
|
||||
|
|
|
@ -983,7 +983,6 @@ gd:
|
|||
delete: Sguab às
|
||||
edit_preset: Deasaich rabhadh ro-shuidhichte
|
||||
empty: Cha do mhìnich thu ro-sheataichean rabhaidhean fhathast.
|
||||
title: Stiùirich na rabhaidhean ro-shuidhichte
|
||||
webhooks:
|
||||
add_new: Cuir puing-dheiridh ris
|
||||
delete: Sguab às
|
||||
|
|
|
@ -951,7 +951,7 @@ gl:
|
|||
delete: Eliminar
|
||||
edit_preset: Editar aviso preestablecido
|
||||
empty: Non definiches os avisos prestablecidos.
|
||||
title: Xestionar avisos preestablecidos
|
||||
title: Preestablecidos de advertencia
|
||||
webhooks:
|
||||
add_new: Engadir punto de extremo
|
||||
delete: Eliminar
|
||||
|
|
|
@ -985,7 +985,7 @@ he:
|
|||
delete: למחוק
|
||||
edit_preset: ערוך/י טקסט מוכן מראש לאזהרה
|
||||
empty: לא הגדרת עדיין שום טקסט מוכן מראש לאזהרה.
|
||||
title: ניהול טקסטים מוכנים מראש לאזהרות
|
||||
title: תצורת אזהרות
|
||||
webhooks:
|
||||
add_new: הוספת נקודת קצה
|
||||
delete: מחיקה
|
||||
|
|
|
@ -951,7 +951,7 @@ hu:
|
|||
delete: Törlés
|
||||
edit_preset: Figyelmeztetés szerkesztése
|
||||
empty: Nem definiáltál még egyetlen figyelmeztetést sem.
|
||||
title: Figyelmeztetések
|
||||
title: Figyelmeztető szövegek
|
||||
webhooks:
|
||||
add_new: Végpont hozzáadása
|
||||
delete: Törlés
|
||||
|
|
|
@ -951,7 +951,7 @@ ia:
|
|||
delete: Deler
|
||||
edit_preset: Rediger aviso predefinite
|
||||
empty: Tu non ha ancora definite alcun avisos predefinite.
|
||||
title: Gerer avisos predefinite
|
||||
title: Predefinitiones de avisos
|
||||
webhooks:
|
||||
add_new: Adder terminal
|
||||
delete: Deler
|
||||
|
|
|
@ -831,7 +831,6 @@ id:
|
|||
delete: Hapus
|
||||
edit_preset: Sunting preset peringatan
|
||||
empty: Anda belum mendefinisikan peringatan apapun.
|
||||
title: Kelola preset peringatan
|
||||
webhooks:
|
||||
add_new: Tambah titik akhir
|
||||
delete: Hapus
|
||||
|
|
|
@ -950,7 +950,6 @@ ie:
|
|||
delete: Deleter
|
||||
edit_preset: Modificar prefiguration de avise
|
||||
empty: Vu ancor ha definit null prefigurationes de avise.
|
||||
title: Modificar prefigurationes de avise
|
||||
webhooks:
|
||||
add_new: Adjunter punctu terminal
|
||||
delete: Deleter
|
||||
|
|
|
@ -928,7 +928,6 @@ io:
|
|||
delete: Efacez
|
||||
edit_preset: Modifikez avertfixito
|
||||
empty: Vu ne fixis irga avertfixito til nun.
|
||||
title: Jerez avertfixiti
|
||||
webhooks:
|
||||
add_new: Insertez finpunto
|
||||
delete: Efacez
|
||||
|
|
|
@ -953,7 +953,7 @@ is:
|
|||
delete: Eyða
|
||||
edit_preset: Breyta forstilltri aðvörun
|
||||
empty: Þú hefur ekki enn skilgreint neinar aðvaranaforstillingar.
|
||||
title: Sýsla með forstilltar aðvaranir
|
||||
title: Forstilltar aðvaranir
|
||||
webhooks:
|
||||
add_new: Bæta við endapunkti
|
||||
delete: Eyða
|
||||
|
|
|
@ -951,7 +951,7 @@ it:
|
|||
delete: Cancella
|
||||
edit_preset: Modifica avviso predefinito
|
||||
empty: Non hai ancora definito alcun avviso preimpostato.
|
||||
title: Gestisci avvisi predefiniti
|
||||
title: Preimpostazioni di avviso
|
||||
webhooks:
|
||||
add_new: Aggiungi endpoint
|
||||
delete: Elimina
|
||||
|
|
|
@ -939,7 +939,6 @@ ja:
|
|||
delete: 削除
|
||||
edit_preset: プリセット警告文を編集
|
||||
empty: まだプリセット警告文が作成されていません。
|
||||
title: プリセット警告文を管理
|
||||
webhooks:
|
||||
add_new: エンドポイントを追加
|
||||
delete: 削除
|
||||
|
|
|
@ -299,7 +299,6 @@ kk:
|
|||
add_new: Add nеw
|
||||
delete: Deletе
|
||||
edit_preset: Edit warning prеset
|
||||
title: Manage warning presеts
|
||||
admin_mailer:
|
||||
new_pending_account:
|
||||
body: Жаңа есептік жазба туралы мәліметтер төменде берілген. Бұл қолданбаны мақұлдауыңызға немесе қабылдамауыңызға болады.
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue