sforkowany z mirror/soapbox
Porównaj commity
9 Commity
develop
...
instancev2
Autor | SHA1 | Data |
---|---|---|
marcin mikołajczak | f7fc865714 | |
marcin mikołajczak | 200b863e0e | |
marcin mikołajczak | 639c563107 | |
marcin mikołajczak | f11702acc7 | |
marcin mikołajczak | 9ee740d065 | |
marcin mikołajczak | c3141c4ae9 | |
marcin mikołajczak | 6fb05cbff4 | |
marcin mikołajczak | ae0a68db36 | |
marcin mikołajczak | 6ecb870c88 |
|
@ -0,0 +1,128 @@
|
||||||
|
{
|
||||||
|
"domain": "mastodon.social",
|
||||||
|
"title": "Mastodon",
|
||||||
|
"version": "4.0.2",
|
||||||
|
"source_url": "https://github.com/mastodon/mastodon",
|
||||||
|
"description": "The original server operated by the Mastodon gGmbH non-profit",
|
||||||
|
"usage": { "users": { "active_month": 227900 } },
|
||||||
|
"thumbnail": {
|
||||||
|
"url": "https://files.mastodon.social/site_uploads/files/000/000/001/@1x/57c12f441d083cde.png",
|
||||||
|
"blurhash": "UeKUpFxuo~R%0nW;WCnhF6RjaJt757oJodS$",
|
||||||
|
"versions": {
|
||||||
|
"@1x": "https://files.mastodon.social/site_uploads/files/000/000/001/@1x/57c12f441d083cde.png",
|
||||||
|
"@2x": "https://files.mastodon.social/site_uploads/files/000/000/001/@2x/57c12f441d083cde.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"languages": ["en"],
|
||||||
|
"configuration": {
|
||||||
|
"urls": { "streaming": "wss://mastodon.social" },
|
||||||
|
"accounts": { "max_featured_tags": 10 },
|
||||||
|
"statuses": {
|
||||||
|
"max_characters": 500,
|
||||||
|
"max_media_attachments": 4,
|
||||||
|
"characters_reserved_per_url": 23
|
||||||
|
},
|
||||||
|
"media_attachments": {
|
||||||
|
"supported_mime_types": [
|
||||||
|
"image/jpeg",
|
||||||
|
"image/png",
|
||||||
|
"image/gif",
|
||||||
|
"image/heic",
|
||||||
|
"image/heif",
|
||||||
|
"image/webp",
|
||||||
|
"image/avif",
|
||||||
|
"video/webm",
|
||||||
|
"video/mp4",
|
||||||
|
"video/quicktime",
|
||||||
|
"video/ogg",
|
||||||
|
"audio/wave",
|
||||||
|
"audio/wav",
|
||||||
|
"audio/x-wav",
|
||||||
|
"audio/x-pn-wave",
|
||||||
|
"audio/vnd.wave",
|
||||||
|
"audio/ogg",
|
||||||
|
"audio/vorbis",
|
||||||
|
"audio/mpeg",
|
||||||
|
"audio/mp3",
|
||||||
|
"audio/webm",
|
||||||
|
"audio/flac",
|
||||||
|
"audio/aac",
|
||||||
|
"audio/m4a",
|
||||||
|
"audio/x-m4a",
|
||||||
|
"audio/mp4",
|
||||||
|
"audio/3gpp",
|
||||||
|
"video/x-ms-asf"
|
||||||
|
],
|
||||||
|
"image_size_limit": 10485760,
|
||||||
|
"image_matrix_limit": 16777216,
|
||||||
|
"video_size_limit": 41943040,
|
||||||
|
"video_frame_rate_limit": 60,
|
||||||
|
"video_matrix_limit": 2304000
|
||||||
|
},
|
||||||
|
"polls": {
|
||||||
|
"max_options": 4,
|
||||||
|
"max_characters_per_option": 50,
|
||||||
|
"min_expiration": 300,
|
||||||
|
"max_expiration": 2629746
|
||||||
|
},
|
||||||
|
"translation": { "enabled": true }
|
||||||
|
},
|
||||||
|
"registrations": {
|
||||||
|
"enabled": false,
|
||||||
|
"approval_required": false,
|
||||||
|
"message": null
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"email": "staff@mastodon.social",
|
||||||
|
"account": {
|
||||||
|
"id": "1",
|
||||||
|
"username": "Gargron",
|
||||||
|
"acct": "Gargron",
|
||||||
|
"display_name": "Eugen Rochko",
|
||||||
|
"locked": false,
|
||||||
|
"bot": false,
|
||||||
|
"discoverable": true,
|
||||||
|
"group": false,
|
||||||
|
"created_at": "2016-03-16T00:00:00.000Z",
|
||||||
|
"note": "\u003cp\u003eFounder, CEO and lead developer \u003cspan class=\"h-card\"\u003e\u003ca href=\"https://mastodon.social/@Mastodon\" class=\"u-url mention\"\u003e@\u003cspan\u003eMastodon\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e, Germany.\u003c/p\u003e",
|
||||||
|
"url": "https://mastodon.social/@Gargron",
|
||||||
|
"avatar": "https://files.mastodon.social/accounts/avatars/000/000/001/original/dc4286ceb8fab734.jpg",
|
||||||
|
"avatar_static": "https://files.mastodon.social/accounts/avatars/000/000/001/original/dc4286ceb8fab734.jpg",
|
||||||
|
"header": "https://files.mastodon.social/accounts/headers/000/000/001/original/3b91c9965d00888b.jpeg",
|
||||||
|
"header_static": "https://files.mastodon.social/accounts/headers/000/000/001/original/3b91c9965d00888b.jpeg",
|
||||||
|
"followers_count": 263431,
|
||||||
|
"following_count": 327,
|
||||||
|
"statuses_count": 72827,
|
||||||
|
"last_status_at": "2022-12-03",
|
||||||
|
"noindex": false,
|
||||||
|
"emojis": [],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "Patreon",
|
||||||
|
"value": "\u003ca href=\"https://www.patreon.com/mastodon\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"\u003e\u003cspan class=\"invisible\"\u003ehttps://www.\u003c/span\u003e\u003cspan class=\"\"\u003epatreon.com/mastodon\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e",
|
||||||
|
"verified_at": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"text": "Sexually explicit or violent media must be marked as sensitive when posting"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"text": "No racism, sexism, homophobia, transphobia, xenophobia, or casteism"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"text": "No incitement of violence or promotion of violent ideologies"
|
||||||
|
},
|
||||||
|
{ "id": "4", "text": "No harassment, dogpiling or doxxing of other users" },
|
||||||
|
{ "id": "5", "text": "No content illegal in Germany" },
|
||||||
|
{
|
||||||
|
"id": "7",
|
||||||
|
"text": "Do not share intentionally false or misleading information"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,10 +1,11 @@
|
||||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
|
import { gte } from 'semver';
|
||||||
|
|
||||||
import KVStore from 'soapbox/storage/kv-store';
|
import KVStore from 'soapbox/storage/kv-store';
|
||||||
import { RootState } from 'soapbox/store';
|
import { RootState } from 'soapbox/store';
|
||||||
import { getAuthUserUrl } from 'soapbox/utils/auth';
|
import { getAuthUserUrl } from 'soapbox/utils/auth';
|
||||||
import { parseVersion } from 'soapbox/utils/features';
|
import { MASTODON, parseVersion, PLEROMA, REBASED } from 'soapbox/utils/features';
|
||||||
|
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
|
|
||||||
|
@ -27,25 +28,48 @@ export const getHost = (state: RootState) => {
|
||||||
export const rememberInstance = createAsyncThunk(
|
export const rememberInstance = createAsyncThunk(
|
||||||
'instance/remember',
|
'instance/remember',
|
||||||
async(host: string) => {
|
async(host: string) => {
|
||||||
return await KVStore.getItemOrError(`instance:${host}`);
|
const instance = await KVStore.getItemOrError(`instance:${host}`);
|
||||||
|
|
||||||
|
return { instance, host };
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const supportsInstanceV2 = (instance: Record<string, any>): boolean => {
|
||||||
|
const v = parseVersion(get(instance, 'version'));
|
||||||
|
return (v.software === MASTODON && gte(v.compatVersion, '4.0.0')) ||
|
||||||
|
(v.software === PLEROMA && v.build === REBASED && gte(v.version, '2.5.0'));
|
||||||
|
};
|
||||||
|
|
||||||
/** We may need to fetch nodeinfo on Pleroma < 2.1 */
|
/** We may need to fetch nodeinfo on Pleroma < 2.1 */
|
||||||
const needsNodeinfo = (instance: Record<string, any>): boolean => {
|
const needsNodeinfo = (instance: Record<string, any>): boolean => {
|
||||||
const v = parseVersion(get(instance, 'version'));
|
const v = parseVersion(get(instance, 'version'));
|
||||||
return v.software === 'Pleroma' && !get(instance, ['pleroma', 'metadata']);
|
return v.software === PLEROMA && !get(instance, ['pleroma', 'metadata']);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchInstance = createAsyncThunk<void, void, { state: RootState }>(
|
export const fetchInstance = createAsyncThunk<{ instance: Record<string, any>, host?: string | null }, string | null | undefined, { state: RootState }>(
|
||||||
'instance/fetch',
|
'instance/fetch',
|
||||||
async(_arg, { dispatch, getState, rejectWithValue }) => {
|
async(host, { dispatch, getState, rejectWithValue }) => {
|
||||||
try {
|
try {
|
||||||
const { data: instance } = await api(getState).get('/api/v1/instance');
|
const { data: instance } = await api(getState).get('/api/v1/instance');
|
||||||
|
if (supportsInstanceV2(instance)) {
|
||||||
|
return dispatch(fetchInstanceV2(host)) as any as { instance: Record<string, any>, host?: string | null };
|
||||||
|
}
|
||||||
if (needsNodeinfo(instance)) {
|
if (needsNodeinfo(instance)) {
|
||||||
dispatch(fetchNodeinfo());
|
dispatch(fetchNodeinfo());
|
||||||
}
|
}
|
||||||
return instance;
|
return { instance, host };
|
||||||
|
} catch (e) {
|
||||||
|
return rejectWithValue(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const fetchInstanceV2 = createAsyncThunk<{ instance: Record<string, any>, host?: string | null }, string | null | undefined, { state: RootState }>(
|
||||||
|
'instance/fetch',
|
||||||
|
async(host, { getState, rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const { data: instance } = await api(getState).get('/api/v2/instance');
|
||||||
|
return { instance, host };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return rejectWithValue(e);
|
return rejectWithValue(e);
|
||||||
}
|
}
|
||||||
|
@ -57,10 +81,11 @@ export const loadInstance = createAsyncThunk<void, void, { state: RootState }>(
|
||||||
'instance/load',
|
'instance/load',
|
||||||
async(_arg, { dispatch, getState }) => {
|
async(_arg, { dispatch, getState }) => {
|
||||||
const host = getHost(getState());
|
const host = getHost(getState());
|
||||||
await Promise.all([
|
const rememberedInstance = await dispatch(rememberInstance(host || ''));
|
||||||
dispatch(rememberInstance(host || '')),
|
|
||||||
dispatch(fetchInstance()),
|
if (rememberedInstance.payload && supportsInstanceV2((rememberedInstance.payload as any).instance)) {
|
||||||
]);
|
await dispatch(fetchInstanceV2(host));
|
||||||
|
} else dispatch(fetchInstance(host));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -146,7 +146,7 @@ const SoapboxMount = () => {
|
||||||
<Route exact path='/about/:slug?' component={PublicLayout} />
|
<Route exact path='/about/:slug?' component={PublicLayout} />
|
||||||
<Route path='/login' component={AuthLayout} />
|
<Route path='/login' component={AuthLayout} />
|
||||||
|
|
||||||
{(features.accountCreation && instance.registrations) && (
|
{(features.accountCreation && instance.registrations.get('enabled')) && (
|
||||||
<Route exact path='/signup' component={AuthLayout} />
|
<Route exact path='/signup' component={AuthLayout} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,8 @@ const generateConfig = (mode: RegistrationMode) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const modeFromInstance = (instance: Instance): RegistrationMode => {
|
const modeFromInstance = (instance: Instance): RegistrationMode => {
|
||||||
if (instance.approval_required && instance.registrations) return 'approval';
|
if (instance.registrations.get('approval_required') && instance.registrations.get('enabled')) return 'approval';
|
||||||
return instance.registrations ? 'open' : 'closed';
|
return instance.registrations.get('enabled') ? 'open' : 'closed';
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Allows changing the registration mode of the instance, eg "open", "closed", "approval" */
|
/** Allows changing the registration mode of the instance, eg "open", "closed", "approval" */
|
||||||
|
|
|
@ -76,7 +76,7 @@ const Ad: React.FC<IAd> = ({ ad }) => {
|
||||||
<Card className='py-6 sm:p-5' variant='rounded'>
|
<Card className='py-6 sm:p-5' variant='rounded'>
|
||||||
<Stack space={4}>
|
<Stack space={4}>
|
||||||
<HStack alignItems='center' space={3}>
|
<HStack alignItems='center' space={3}>
|
||||||
<Avatar src={instance.thumbnail} size={42} />
|
<Avatar src={instance.thumbnail.get('url')} size={42} />
|
||||||
|
|
||||||
<Stack grow>
|
<Stack grow>
|
||||||
<HStack space={1}>
|
<HStack space={1}>
|
||||||
|
|
|
@ -46,7 +46,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
|
||||||
|
|
||||||
const locale = settings.get('locale');
|
const locale = settings.get('locale');
|
||||||
const needsConfirmation = !!instance.pleroma.getIn(['metadata', 'account_activation_required']);
|
const needsConfirmation = !!instance.pleroma.getIn(['metadata', 'account_activation_required']);
|
||||||
const needsApproval = instance.approval_required;
|
const needsApproval = instance.registrations.get('approval_required');
|
||||||
const supportsEmailList = features.emailList;
|
const supportsEmailList = features.emailList;
|
||||||
const supportsAccountLookup = features.accountLookup;
|
const supportsAccountLookup = features.accountLookup;
|
||||||
const birthdayRequired = instance.pleroma.getIn(['metadata', 'birthday_required']);
|
const birthdayRequired = instance.pleroma.getIn(['metadata', 'birthday_required']);
|
||||||
|
|
|
@ -13,7 +13,9 @@ interface IUploadCompose {
|
||||||
const UploadCompose: React.FC<IUploadCompose> = ({ composeId, id }) => {
|
const UploadCompose: React.FC<IUploadCompose> = ({ composeId, id }) => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { description_limit: descriptionLimit } = useInstance();
|
const { pleroma } = useInstance();
|
||||||
|
|
||||||
|
const descriptionLimit = pleroma.getIn(['metadata', 'description_limit']) as number;
|
||||||
|
|
||||||
const media = useCompose(composeId).media_attachments.find(item => item.id === id)!;
|
const media = useCompose(composeId).media_attachments.find(item => item.id === id)!;
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ describe('<FeedCarousel />', () => {
|
||||||
features: [],
|
features: [],
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
configuration: ImmutableMap({}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,8 +12,10 @@ describe('<LandingPage />', () => {
|
||||||
const state = rootReducer(undefined, {
|
const state = rootReducer(undefined, {
|
||||||
type: rememberInstance.fulfilled.type,
|
type: rememberInstance.fulfilled.type,
|
||||||
payload: {
|
payload: {
|
||||||
version: '2.7.2 (compatible; Pleroma 2.3.0)',
|
instance: {
|
||||||
registrations: true,
|
version: '2.7.2 (compatible; Pleroma 2.3.0)',
|
||||||
|
registrations: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -29,8 +31,10 @@ describe('<LandingPage />', () => {
|
||||||
const state = rootReducer(undefined, {
|
const state = rootReducer(undefined, {
|
||||||
type: rememberInstance.fulfilled.type,
|
type: rememberInstance.fulfilled.type,
|
||||||
payload: {
|
payload: {
|
||||||
version: '2.7.2 (compatible; Pleroma 2.3.0)',
|
instance: {
|
||||||
registrations: false,
|
version: '2.7.2 (compatible; Pleroma 2.3.0)',
|
||||||
|
registrations: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -71,8 +75,10 @@ describe('<LandingPage />', () => {
|
||||||
const state = applyActions(undefined, [{
|
const state = applyActions(undefined, [{
|
||||||
type: rememberInstance.fulfilled.type,
|
type: rememberInstance.fulfilled.type,
|
||||||
payload: {
|
payload: {
|
||||||
version: '3.4.1 (compatible; TruthSocial 1.0.0)',
|
instance: {
|
||||||
registrations: false,
|
version: '3.4.1 (compatible; TruthSocial 1.0.0)',
|
||||||
|
registrations: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
type: PEPE_FETCH_INSTANCE_SUCCESS,
|
type: PEPE_FETCH_INSTANCE_SUCCESS,
|
||||||
|
|
|
@ -93,7 +93,7 @@ const LandingPage = () => {
|
||||||
return renderProvider();
|
return renderProvider();
|
||||||
} else if (pepeEnabled && pepeOpen) {
|
} else if (pepeEnabled && pepeOpen) {
|
||||||
return renderPepe();
|
return renderPepe();
|
||||||
} else if (features.accountCreation && instance.registrations) {
|
} else if (features.accountCreation && instance.registrations.get('enabled')) {
|
||||||
return renderOpen();
|
return renderOpen();
|
||||||
} else {
|
} else {
|
||||||
return renderClosed();
|
return renderClosed();
|
||||||
|
@ -113,7 +113,7 @@ const LandingPage = () => {
|
||||||
|
|
||||||
<Markup
|
<Markup
|
||||||
size='lg'
|
size='lg'
|
||||||
dangerouslySetInnerHTML={{ __html: instance.short_description || instance.description }}
|
dangerouslySetInnerHTML={{ __html: instance.description }}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -359,7 +359,7 @@ const UI: React.FC<IUI> = ({ children }) => {
|
||||||
|
|
||||||
const dropdownMenuIsOpen = useAppSelector(state => state.dropdown_menu.openId !== null);
|
const dropdownMenuIsOpen = useAppSelector(state => state.dropdown_menu.openId !== null);
|
||||||
const accessToken = useAppSelector(state => getAccessToken(state));
|
const accessToken = useAppSelector(state => getAccessToken(state));
|
||||||
const streamingUrl = instance.urls.get('streaming_api');
|
const streamingUrl = instance.configuration.getIn(['urls', 'streaming']);
|
||||||
const standalone = useAppSelector(isStandalone);
|
const standalone = useAppSelector(isStandalone);
|
||||||
|
|
||||||
const handleDragEnter = (e: DragEvent) => {
|
const handleDragEnter = (e: DragEvent) => {
|
||||||
|
|
|
@ -13,10 +13,10 @@ export const useRegistrationStatus = () => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/** Registrations are open, either through Pepe or traditional account creation. */
|
/** Registrations are open, either through Pepe or traditional account creation. */
|
||||||
isOpen: (features.accountCreation && instance.registrations) || (pepeEnabled && pepeOpen),
|
isOpen: (features.accountCreation && instance.registrations.get('enabled')) || (pepeEnabled && pepeOpen),
|
||||||
/** Whether Pepe is open. */
|
/** Whether Pepe is open. */
|
||||||
pepeOpen,
|
pepeOpen,
|
||||||
/** Whether Pepe is enabled. */
|
/** Whether Pepe is enabled. */
|
||||||
pepeEnabled,
|
pepeEnabled,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,8 +5,6 @@ import { normalizeInstance } from '../instance';
|
||||||
describe('normalizeInstance()', () => {
|
describe('normalizeInstance()', () => {
|
||||||
it('normalizes an empty Map', () => {
|
it('normalizes an empty Map', () => {
|
||||||
const expected = {
|
const expected = {
|
||||||
approval_required: false,
|
|
||||||
contact_account: {},
|
|
||||||
configuration: {
|
configuration: {
|
||||||
media_attachments: {},
|
media_attachments: {},
|
||||||
chats: {
|
chats: {
|
||||||
|
@ -22,13 +20,22 @@ describe('normalizeInstance()', () => {
|
||||||
max_characters: 500,
|
max_characters: 500,
|
||||||
max_media_attachments: 4,
|
max_media_attachments: 4,
|
||||||
},
|
},
|
||||||
|
translation: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
urls: {
|
||||||
|
streaming: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
contact: {
|
||||||
|
account: {},
|
||||||
|
email: '',
|
||||||
},
|
},
|
||||||
description: '',
|
description: '',
|
||||||
description_limit: 1500,
|
domain: '',
|
||||||
email: '',
|
email: '',
|
||||||
feature_quote: false,
|
feature_quote: false,
|
||||||
fedibird_capabilities: [],
|
fedibird_capabilities: [],
|
||||||
invites_enabled: false,
|
|
||||||
languages: [],
|
languages: [],
|
||||||
login_message: '',
|
login_message: '',
|
||||||
pleroma: {
|
pleroma: {
|
||||||
|
@ -36,6 +43,7 @@ describe('normalizeInstance()', () => {
|
||||||
account_activation_required: false,
|
account_activation_required: false,
|
||||||
birthday_min_age: 0,
|
birthday_min_age: 0,
|
||||||
birthday_required: false,
|
birthday_required: false,
|
||||||
|
description_limit: 1500,
|
||||||
features: [],
|
features: [],
|
||||||
federation: {
|
federation: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
@ -44,18 +52,23 @@ describe('normalizeInstance()', () => {
|
||||||
},
|
},
|
||||||
stats: {},
|
stats: {},
|
||||||
},
|
},
|
||||||
registrations: false,
|
registrations: {
|
||||||
rules: [],
|
approval_required: false,
|
||||||
short_description: '',
|
enabled: false,
|
||||||
stats: {
|
message: '',
|
||||||
domain_count: 0,
|
|
||||||
status_count: 0,
|
|
||||||
user_count: 0,
|
|
||||||
},
|
},
|
||||||
|
rules: [],
|
||||||
|
source_url: '',
|
||||||
|
stats: {},
|
||||||
title: '',
|
title: '',
|
||||||
thumbnail: '',
|
thumbnail: {
|
||||||
uri: '',
|
url: '',
|
||||||
urls: {},
|
},
|
||||||
|
usage: {
|
||||||
|
users: {
|
||||||
|
active_month: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
version: '0.0.0',
|
version: '0.0.0',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -142,7 +155,7 @@ describe('normalizeInstance()', () => {
|
||||||
const result = normalizeInstance(instance);
|
const result = normalizeInstance(instance);
|
||||||
|
|
||||||
// Sets description_limit
|
// Sets description_limit
|
||||||
expect(result.description_limit).toEqual(1500);
|
expect(result.pleroma.getIn(['metadata', 'description_limit'])).toEqual(1500);
|
||||||
|
|
||||||
// Preserves fedibird_capabilities
|
// Preserves fedibird_capabilities
|
||||||
expect(result.fedibird_capabilities).toEqual(fromJS(instance.fedibird_capabilities));
|
expect(result.fedibird_capabilities).toEqual(fromJS(instance.fedibird_capabilities));
|
||||||
|
@ -154,7 +167,7 @@ describe('normalizeInstance()', () => {
|
||||||
|
|
||||||
// Adds configuration and description_limit
|
// Adds configuration and description_limit
|
||||||
expect(result.get('configuration') instanceof ImmutableMap).toBe(true);
|
expect(result.get('configuration') instanceof ImmutableMap).toBe(true);
|
||||||
expect(result.get('description_limit')).toBe(1500);
|
expect(result.pleroma.getIn(['metadata', 'description_limit'])).toBe(1500);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('normalizes GoToSocial instance', () => {
|
it('normalizes GoToSocial instance', () => {
|
||||||
|
@ -167,7 +180,7 @@ describe('normalizeInstance()', () => {
|
||||||
|
|
||||||
// Adds configuration and description_limit
|
// Adds configuration and description_limit
|
||||||
expect(result.get('configuration') instanceof ImmutableMap).toBe(true);
|
expect(result.get('configuration') instanceof ImmutableMap).toBe(true);
|
||||||
expect(result.get('description_limit')).toBe(1500);
|
expect(result.pleroma.getIn(['metadata', 'description_limit'])).toBe(1500);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('normalizes Friendica instance', () => {
|
it('normalizes Friendica instance', () => {
|
||||||
|
@ -180,7 +193,7 @@ describe('normalizeInstance()', () => {
|
||||||
|
|
||||||
// Adds configuration and description_limit
|
// Adds configuration and description_limit
|
||||||
expect(result.get('configuration') instanceof ImmutableMap).toBe(true);
|
expect(result.get('configuration') instanceof ImmutableMap).toBe(true);
|
||||||
expect(result.get('description_limit')).toBe(1500);
|
expect(result.pleroma.getIn(['metadata', 'description_limit'])).toBe(1500);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('normalizes a Mastodon RC version', () => {
|
it('normalizes a Mastodon RC version', () => {
|
||||||
|
|
|
@ -17,8 +17,10 @@ import { isNumber } from 'soapbox/utils/numbers';
|
||||||
// Use Mastodon defaults
|
// Use Mastodon defaults
|
||||||
// https://docs.joinmastodon.org/entities/instance/
|
// https://docs.joinmastodon.org/entities/instance/
|
||||||
export const InstanceRecord = ImmutableRecord({
|
export const InstanceRecord = ImmutableRecord({
|
||||||
approval_required: false,
|
contact: ImmutableMap<string, any>({
|
||||||
contact_account: ImmutableMap<string, any>(),
|
account: ImmutableMap<string, any>(),
|
||||||
|
email: '',
|
||||||
|
}),
|
||||||
configuration: ImmutableMap<string, any>({
|
configuration: ImmutableMap<string, any>({
|
||||||
media_attachments: ImmutableMap<string, any>(),
|
media_attachments: ImmutableMap<string, any>(),
|
||||||
chats: ImmutableMap<string, number>({
|
chats: ImmutableMap<string, number>({
|
||||||
|
@ -34,15 +36,31 @@ export const InstanceRecord = ImmutableRecord({
|
||||||
max_characters: 500,
|
max_characters: 500,
|
||||||
max_media_attachments: 4,
|
max_media_attachments: 4,
|
||||||
}),
|
}),
|
||||||
|
translation: ImmutableMap<string, any>({ enabled: false }),
|
||||||
|
urls: ImmutableMap<string, string>(),
|
||||||
}),
|
}),
|
||||||
description: '',
|
description: '',
|
||||||
description_limit: 1500,
|
domain: '',
|
||||||
email: '',
|
email: '',
|
||||||
feature_quote: false,
|
|
||||||
fedibird_capabilities: ImmutableList(),
|
|
||||||
invites_enabled: false,
|
|
||||||
languages: ImmutableList(),
|
languages: ImmutableList(),
|
||||||
login_message: '',
|
registrations: ImmutableMap<string, any>({
|
||||||
|
approval_required: false,
|
||||||
|
enabled: false,
|
||||||
|
message: '',
|
||||||
|
|
||||||
|
}),
|
||||||
|
rules: ImmutableList(),
|
||||||
|
source_url: '',
|
||||||
|
stats: ImmutableMap<string, number>(),
|
||||||
|
title: '',
|
||||||
|
thumbnail: ImmutableMap<string, any>(),
|
||||||
|
usage: ImmutableMap<string, any>({
|
||||||
|
users: ImmutableMap<string, number>({
|
||||||
|
active_month: 0,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
version: '0.0.0',
|
||||||
|
|
||||||
pleroma: ImmutableMap<string, any>({
|
pleroma: ImmutableMap<string, any>({
|
||||||
metadata: ImmutableMap<string, any>({
|
metadata: ImmutableMap<string, any>({
|
||||||
account_activation_required: false,
|
account_activation_required: false,
|
||||||
|
@ -53,22 +71,15 @@ export const InstanceRecord = ImmutableRecord({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
exclusions: false,
|
exclusions: false,
|
||||||
}),
|
}),
|
||||||
|
description_limit: 1500,
|
||||||
}),
|
}),
|
||||||
stats: ImmutableMap(),
|
stats: ImmutableMap(),
|
||||||
}),
|
}),
|
||||||
registrations: false,
|
|
||||||
rules: ImmutableList(),
|
feature_quote: false,
|
||||||
short_description: '',
|
fedibird_capabilities: ImmutableList(),
|
||||||
stats: ImmutableMap<string, number>({
|
|
||||||
domain_count: 0,
|
login_message: '',
|
||||||
status_count: 0,
|
|
||||||
user_count: 0,
|
|
||||||
}),
|
|
||||||
title: '',
|
|
||||||
thumbnail: '',
|
|
||||||
uri: '',
|
|
||||||
urls: ImmutableMap<string, string>(),
|
|
||||||
version: '0.0.0',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Build Mastodon configuration from Pleroma instance
|
// Build Mastodon configuration from Pleroma instance
|
||||||
|
@ -123,10 +134,40 @@ const fixTakahe = (instance: ImmutableMap<string, any>) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Convert /api/v1/instance format to v2 */
|
||||||
|
const fixInstanceV1 = (instance: ImmutableMap<string, any>) => {
|
||||||
|
instance.setIn(['configuration', 'urls', 'streaming'], instance.getIn(['urls', 'streaming_api'], ''));
|
||||||
|
|
||||||
|
instance.set('contact', ImmutableMap({
|
||||||
|
account: instance.get('contact_account'),
|
||||||
|
email: instance.get('email'),
|
||||||
|
}));
|
||||||
|
|
||||||
|
instance.set('description', instance.get('short_description') || instance.get('description'));
|
||||||
|
|
||||||
|
instance.set('registrations', ImmutableMap({
|
||||||
|
approval_required: instance.get('approval_required'),
|
||||||
|
enabled: instance.get('registrations'),
|
||||||
|
message: '',
|
||||||
|
}));
|
||||||
|
|
||||||
|
instance.set('thumbnail', ImmutableMap({
|
||||||
|
url: instance.get('thumbnail', ''),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (instance.has('pleroma')) {
|
||||||
|
instance.setIn(['pleroma', 'metadata', 'description_limit'], instance.get('description_limit'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Normalize instance (Pleroma, Mastodon, etc.) to Mastodon's format
|
// Normalize instance (Pleroma, Mastodon, etc.) to Mastodon's format
|
||||||
export const normalizeInstance = (instance: Record<string, any>) => {
|
export const normalizeInstance = (instance: Record<string, any>) => {
|
||||||
return InstanceRecord(
|
return InstanceRecord(
|
||||||
ImmutableMap(fromJS(instance)).withMutations((instance: ImmutableMap<string, any>) => {
|
ImmutableMap(fromJS(instance)).withMutations((instance: ImmutableMap<string, any>) => {
|
||||||
|
const version = instance.has('domain') ? 'v2' : 'v1';
|
||||||
|
|
||||||
|
if (version === 'v1') fixInstanceV1(instance);
|
||||||
|
|
||||||
const { software } = parseVersion(instance.get('version'));
|
const { software } = parseVersion(instance.get('version'));
|
||||||
const mastodonConfig = pleromaToMastodonConfig(instance);
|
const mastodonConfig = pleromaToMastodonConfig(instance);
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ describe('instance reducer', () => {
|
||||||
const result = reducer(undefined, {} as any);
|
const result = reducer(undefined, {} as any);
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
description_limit: 1500,
|
|
||||||
configuration: {
|
configuration: {
|
||||||
chats: {
|
chats: {
|
||||||
max_characters: 500,
|
max_characters: 500,
|
||||||
|
@ -37,7 +36,7 @@ describe('instance reducer', () => {
|
||||||
it('normalizes Pleroma instance with Mastodon configuration format', () => {
|
it('normalizes Pleroma instance with Mastodon configuration format', () => {
|
||||||
const action = {
|
const action = {
|
||||||
type: rememberInstance.fulfilled.type,
|
type: rememberInstance.fulfilled.type,
|
||||||
payload: require('soapbox/__fixtures__/pleroma-instance.json'),
|
payload: { instance: require('soapbox/__fixtures__/pleroma-instance.json') },
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = reducer(undefined, action);
|
const result = reducer(undefined, action);
|
||||||
|
@ -63,7 +62,7 @@ describe('instance reducer', () => {
|
||||||
it('normalizes Mastodon instance with retained configuration', () => {
|
it('normalizes Mastodon instance with retained configuration', () => {
|
||||||
const action = {
|
const action = {
|
||||||
type: rememberInstance.fulfilled.type,
|
type: rememberInstance.fulfilled.type,
|
||||||
payload: require('soapbox/__fixtures__/mastodon-instance.json'),
|
payload: { instance: require('soapbox/__fixtures__/mastodon-instance.json') },
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = reducer(undefined, action);
|
const result = reducer(undefined, action);
|
||||||
|
@ -97,7 +96,7 @@ describe('instance reducer', () => {
|
||||||
it('normalizes Mastodon 3.0.0 instance with default configuration', () => {
|
it('normalizes Mastodon 3.0.0 instance with default configuration', () => {
|
||||||
const action = {
|
const action = {
|
||||||
type: rememberInstance.fulfilled.type,
|
type: rememberInstance.fulfilled.type,
|
||||||
payload: require('soapbox/__fixtures__/mastodon-3.0.0-instance.json'),
|
payload: { instance: require('soapbox/__fixtures__/mastodon-3.0.0-instance.json') },
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = reducer(undefined, action);
|
const result = reducer(undefined, action);
|
||||||
|
@ -119,6 +118,40 @@ describe('instance reducer', () => {
|
||||||
|
|
||||||
expect(result.toJS()).toMatchObject(expected);
|
expect(result.toJS()).toMatchObject(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('normalizes Mastodon 4.0.2 instance fetched with v2 endpoint', () => {
|
||||||
|
const action = {
|
||||||
|
type: rememberInstance.fulfilled.type,
|
||||||
|
payload: { instance: require('soapbox/__fixtures__/mastodon-instance-v2.json') },
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = reducer(undefined, action);
|
||||||
|
|
||||||
|
const expected = {
|
||||||
|
configuration: {
|
||||||
|
statuses: {
|
||||||
|
max_characters: 500,
|
||||||
|
max_media_attachments: 4,
|
||||||
|
},
|
||||||
|
polls: {
|
||||||
|
max_options: 4,
|
||||||
|
max_characters_per_option: 50,
|
||||||
|
min_expiration: 300,
|
||||||
|
max_expiration: 2629746,
|
||||||
|
},
|
||||||
|
translation: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
registrations: {
|
||||||
|
enabled: false,
|
||||||
|
approval_required: false,
|
||||||
|
message: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(result.toJS()).toMatchObject(expected);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ADMIN_CONFIG_UPDATE_REQUEST', () => {
|
describe('ADMIN_CONFIG_UPDATE_REQUEST', () => {
|
||||||
|
@ -130,13 +163,13 @@ describe('instance reducer', () => {
|
||||||
configs,
|
configs,
|
||||||
};
|
};
|
||||||
|
|
||||||
// The normalizer has `registrations: closed` by default
|
// The normalizer has `registrations.enabled: closed` by default
|
||||||
const state = reducer(undefined, {} as any);
|
const state = reducer(undefined, {} as any);
|
||||||
expect(state.registrations).toBe(false);
|
expect(state.registrations.get('enabled')).toBe(false);
|
||||||
|
|
||||||
// After importing the configs, registration will be open
|
// After importing the configs, registration will be open
|
||||||
const result = reducer(state, action);
|
const result = reducer(state, action);
|
||||||
expect(result.registrations).toBe(true);
|
expect(result.registrations.get('enabled')).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -65,8 +65,8 @@ const importConfigs = (state: typeof initialState, configs: ImmutableList<any>)
|
||||||
const registrationsOpen = getConfigValue(value, ':registrations_open');
|
const registrationsOpen = getConfigValue(value, ':registrations_open');
|
||||||
const approvalRequired = getConfigValue(value, ':account_approval_required');
|
const approvalRequired = getConfigValue(value, ':account_approval_required');
|
||||||
|
|
||||||
state.update('registrations', c => typeof registrationsOpen === 'boolean' ? registrationsOpen : c);
|
state.updateIn(['registrations', 'enabled'], c => typeof registrationsOpen === 'boolean' ? registrationsOpen : c);
|
||||||
state.update('approval_required', c => typeof approvalRequired === 'boolean' ? approvalRequired : c);
|
state.updateIn(['registrations', 'approval_required'], c => typeof approvalRequired === 'boolean' ? approvalRequired : c);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (simplePolicy) {
|
if (simplePolicy) {
|
||||||
|
@ -95,8 +95,7 @@ const getHost = (instance: { uri: string }) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const persistInstance = (instance: { uri: string }) => {
|
const persistInstance = (instance: { uri: string }, host: string | null = getHost(instance)) => {
|
||||||
const host = getHost(instance);
|
|
||||||
|
|
||||||
if (host) {
|
if (host) {
|
||||||
KVStore.setItem(`instance:${host}`, instance).catch(console.error);
|
KVStore.setItem(`instance:${host}`, instance).catch(console.error);
|
||||||
|
@ -116,14 +115,14 @@ export default function instance(state = initialState, action: AnyAction) {
|
||||||
case PLEROMA_PRELOAD_IMPORT:
|
case PLEROMA_PRELOAD_IMPORT:
|
||||||
return preloadImport(state, action, '/api/v1/instance');
|
return preloadImport(state, action, '/api/v1/instance');
|
||||||
case rememberInstance.fulfilled.type:
|
case rememberInstance.fulfilled.type:
|
||||||
return importInstance(state, ImmutableMap(fromJS(action.payload)));
|
return importInstance(state, ImmutableMap(fromJS(action.payload.instance)));
|
||||||
case fetchInstance.fulfilled.type:
|
case fetchInstance.fulfilled.type:
|
||||||
persistInstance(action.payload);
|
persistInstance(action.payload);
|
||||||
return importInstance(state, ImmutableMap(fromJS(action.payload)));
|
return importInstance(state, ImmutableMap(fromJS(action.payload.instance)));
|
||||||
case fetchInstance.rejected.type:
|
case fetchInstance.rejected.type:
|
||||||
return handleInstanceFetchFail(state, action.error);
|
return handleInstanceFetchFail(state, action.error);
|
||||||
case fetchNodeinfo.fulfilled.type:
|
case fetchNodeinfo.fulfilled.type:
|
||||||
return importNodeinfo(state, ImmutableMap(fromJS(action.payload)));
|
return importNodeinfo(state, ImmutableMap(fromJS(action.payload.instance)));
|
||||||
case ADMIN_CONFIG_UPDATE_REQUEST:
|
case ADMIN_CONFIG_UPDATE_REQUEST:
|
||||||
case ADMIN_CONFIG_UPDATE_SUCCESS:
|
case ADMIN_CONFIG_UPDATE_SUCCESS:
|
||||||
return importConfigs(state, ImmutableList(fromJS(action.configs)));
|
return importConfigs(state, ImmutableList(fromJS(action.configs)));
|
||||||
|
|
|
@ -14,7 +14,7 @@ export function connectStream(
|
||||||
callbacks: (dispatch: AppDispatch, getState: () => RootState) => Record<string, any> = () => ({ onConnect() {}, onDisconnect() {}, onReceive() {} }),
|
callbacks: (dispatch: AppDispatch, getState: () => RootState) => Record<string, any> = () => ({ onConnect() {}, onDisconnect() {}, onReceive() {} }),
|
||||||
) {
|
) {
|
||||||
return (dispatch: AppDispatch, getState: () => RootState) => {
|
return (dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
const streamingAPIBaseURL = getState().instance.urls.get('streaming_api');
|
const streamingAPIBaseURL = getState().instance.configuration.getIn(['urls', 'streaming']) as string;
|
||||||
const accessToken = getAccessToken(getState());
|
const accessToken = getAccessToken(getState());
|
||||||
const { onConnect, onDisconnect, onReceive } = callbacks(dispatch, getState);
|
const { onConnect, onDisconnect, onReceive } = callbacks(dispatch, getState);
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ export function connectStream(
|
||||||
// If the WebSocket fails to be created, don't crash the whole page,
|
// If the WebSocket fails to be created, don't crash the whole page,
|
||||||
// just proceed without a subscription.
|
// just proceed without a subscription.
|
||||||
try {
|
try {
|
||||||
subscription = getStream(streamingAPIBaseURL!, accessToken, path, {
|
subscription = getStream(streamingAPIBaseURL, accessToken, path, {
|
||||||
connected() {
|
connected() {
|
||||||
if (pollingRefresh) {
|
if (pollingRefresh) {
|
||||||
clearPolling();
|
clearPolling();
|
||||||
|
|
|
@ -802,7 +802,7 @@ const getInstanceFeatures = (instance: Instance) => {
|
||||||
* Can translate statuses.
|
* Can translate statuses.
|
||||||
* @see POST /api/v1/statuses/:id/translate
|
* @see POST /api/v1/statuses/:id/translate
|
||||||
*/
|
*/
|
||||||
translations: features.includes('translation'),
|
translations: features.includes('translation') || instance.configuration.getIn(['translation', 'enabled']),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trending statuses.
|
* Trending statuses.
|
||||||
|
|
Ładowanie…
Reference in New Issue