Merge remote-tracking branch 'origin/main' into instance-v2

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak
2024-01-05 00:33:40 +01:00
96 changed files with 4363 additions and 2729 deletions

View File

@@ -9,7 +9,7 @@ import trimStart from 'lodash/trimStart';
import { type MRFSimple, mrfSimpleSchema } from 'soapbox/schemas/pleroma';
export type Config = ImmutableMap<string, any>;
export type Policy = ImmutableMap<string, any>;
export type Policy = Record<string, any>;
const find = (
configs: ImmutableList<Config>,
@@ -40,15 +40,15 @@ const toSimplePolicy = (configs: ImmutableList<Config>): MRFSimple => {
};
const fromSimplePolicy = (simplePolicy: Policy): ImmutableList<Config> => {
const mapper = (hosts: ImmutableList<string>, key: string) => fromJS({ tuple: [`:${key}`, hosts.toJS()] });
const mapper = ([key, hosts]: [key: string, hosts: ImmutableList<string>]) => fromJS({ tuple: [`:${key}`, hosts] });
const value = simplePolicy.map(mapper).toList();
const value = Object.entries(simplePolicy).map(mapper);
return ImmutableList([
ImmutableMap({
group: ':pleroma',
key: ':mrf_simple',
value,
value: ImmutableList(value),
}),
]);
};

View File

@@ -1,11 +1,11 @@
import { List as ImmutableList, Map as ImmutableMap, fromJS } from 'immutable';
import { List as ImmutableList, fromJS } from 'immutable';
import { normalizeStatus } from 'soapbox/normalizers';
import { emojiReactionSchema } from 'soapbox/schemas';
import {
sortEmoji,
mergeEmojiFavourites,
oneEmojiPerAccount,
reduceEmoji,
getReactForStatus,
simulateEmojiReact,
@@ -23,7 +23,7 @@ const ALLOWED_EMOJI = ImmutableList([
describe('sortEmoji', () => {
describe('with an unsorted list of emoji', () => {
const emojiReacts = fromJS([
const emojiReacts = ImmutableList([
{ 'count': 7, 'me': true, 'name': '😃' },
{ 'count': 7, 'me': true, 'name': '😯' },
{ 'count': 3, 'me': true, 'name': '😢' },
@@ -31,7 +31,7 @@ describe('sortEmoji', () => {
{ 'count': 20, 'me': true, 'name': '👍' },
{ 'count': 7, 'me': true, 'name': '😂' },
{ 'count': 15, 'me': true, 'name': '❤' },
]) as ImmutableList<ImmutableMap<string, any>>;
].map((react) => emojiReactionSchema.parse(react)));
it('sorts the emoji by count', () => {
expect(sortEmoji(emojiReacts, ALLOWED_EMOJI)).toEqual(fromJS([
{ 'count': 20, 'me': true, 'name': '👍' },
@@ -51,11 +51,11 @@ describe('mergeEmojiFavourites', () => {
const favourited = true;
describe('with existing 👍 reacts', () => {
const emojiReacts = fromJS([
const emojiReacts = ImmutableList([
{ 'count': 20, 'me': false, 'name': '👍', 'url': undefined },
{ 'count': 15, 'me': false, 'name': '❤', 'url': undefined },
{ 'count': 7, 'me': false, 'name': '😯', 'url': undefined },
]) as ImmutableList<ImmutableMap<string, any>>;
].map((react) => emojiReactionSchema.parse(react)));
it('combines 👍 reacts with favourites', () => {
expect(mergeEmojiFavourites(emojiReacts, favouritesCount, favourited)).toEqual(fromJS([
{ 'count': 32, 'me': true, 'name': '👍', 'url': undefined },
@@ -66,10 +66,10 @@ describe('mergeEmojiFavourites', () => {
});
describe('without existing 👍 reacts', () => {
const emojiReacts = fromJS([
const emojiReacts = ImmutableList([
{ 'count': 15, 'me': false, 'name': '❤' },
{ 'count': 7, 'me': false, 'name': '😯' },
]) as ImmutableList<ImmutableMap<string, any>>;
].map((react) => emojiReactionSchema.parse(react)));
it('adds 👍 reacts to the map equaling favourite count', () => {
expect(mergeEmojiFavourites(emojiReacts, favouritesCount, favourited)).toEqual(fromJS([
{ 'count': 15, 'me': false, 'name': '❤' },
@@ -88,7 +88,7 @@ describe('mergeEmojiFavourites', () => {
describe('reduceEmoji', () => {
describe('with a clusterfuck of emoji', () => {
const emojiReacts = fromJS([
const emojiReacts = ImmutableList([
{ 'count': 1, 'me': false, 'name': '😡' },
{ 'count': 1, 'me': true, 'name': '🔪' },
{ 'count': 7, 'me': true, 'name': '😯' },
@@ -99,7 +99,7 @@ describe('reduceEmoji', () => {
{ 'count': 15, 'me': true, 'name': '❤' },
{ 'count': 1, 'me': false, 'name': '👀' },
{ 'count': 1, 'me': false, 'name': '🍩' },
]) as ImmutableList<ImmutableMap<string, any>>;
].map((react) => emojiReactionSchema.parse(react)));
it('sorts, filters, and combines emoji and favourites', () => {
expect(reduceEmoji(emojiReacts, 7, true, ALLOWED_EMOJI)).toEqual(fromJS([
{ 'count': 27, 'me': true, 'name': '👍' },
@@ -117,22 +117,6 @@ describe('reduceEmoji', () => {
});
});
describe('oneEmojiPerAccount', () => {
it('reduces to one react per account', () => {
const emojiReacts = fromJS([
// Sorted
{ 'count': 2, 'me': true, 'name': '👍', accounts: [{ id: '1' }, { id: '2' }] },
{ 'count': 2, 'me': true, 'name': '❤', accounts: [{ id: '1' }, { id: '2' }] },
{ 'count': 1, 'me': true, 'name': '😯', accounts: [{ id: '1' }] },
{ 'count': 1, 'me': false, 'name': '😂', accounts: [{ id: '3' }] },
]) as ImmutableList<ImmutableMap<string, any>>;
expect(oneEmojiPerAccount(emojiReacts, '1')).toEqual(fromJS([
{ 'count': 2, 'me': true, 'name': '👍', accounts: [{ id: '1' }, { id: '2' }] },
{ 'count': 1, 'me': false, 'name': '😂', accounts: [{ id: '3' }] },
]));
});
});
describe('getReactForStatus', () => {
it('returns a single owned react (including favourite) for the status', () => {
const status = normalizeStatus(fromJS({
@@ -146,12 +130,12 @@ describe('getReactForStatus', () => {
],
},
}));
expect(getReactForStatus(status, ALLOWED_EMOJI)?.get('name')).toEqual('❤');
expect(getReactForStatus(status, ALLOWED_EMOJI)?.name).toEqual('❤');
});
it('returns a thumbs-up for a favourite', () => {
const status = normalizeStatus(fromJS({ favourites_count: 1, favourited: true }));
expect(getReactForStatus(status)?.get('name')).toEqual('👍');
expect(getReactForStatus(status)?.name).toEqual('👍');
});
it('returns undefined when a status has no reacts (or favourites)', () => {
@@ -172,10 +156,10 @@ describe('getReactForStatus', () => {
describe('simulateEmojiReact', () => {
it('adds the emoji to the list', () => {
const emojiReacts = fromJS([
const emojiReacts = ImmutableList([
{ 'count': 2, 'me': false, 'name': '👍', 'url': undefined },
{ 'count': 2, 'me': false, 'name': '❤', 'url': undefined },
]) as ImmutableList<ImmutableMap<string, any>>;
].map((react) => emojiReactionSchema.parse(react)));
expect(simulateEmojiReact(emojiReacts, '❤')).toEqual(fromJS([
{ 'count': 2, 'me': false, 'name': '👍', 'url': undefined },
{ 'count': 3, 'me': true, 'name': '❤', 'url': undefined },
@@ -183,10 +167,10 @@ describe('simulateEmojiReact', () => {
});
it('creates the emoji if it didn\'t already exist', () => {
const emojiReacts = fromJS([
const emojiReacts = ImmutableList([
{ 'count': 2, 'me': false, 'name': '👍', 'url': undefined },
{ 'count': 2, 'me': false, 'name': '❤', 'url': undefined },
]) as ImmutableList<ImmutableMap<string, any>>;
].map((react) => emojiReactionSchema.parse(react)));
expect(simulateEmojiReact(emojiReacts, '😯')).toEqual(fromJS([
{ 'count': 2, 'me': false, 'name': '👍', 'url': undefined },
{ 'count': 2, 'me': false, 'name': '❤', 'url': undefined },
@@ -195,10 +179,10 @@ describe('simulateEmojiReact', () => {
});
it('adds a custom emoji to the list', () => {
const emojiReacts = fromJS([
const emojiReacts = ImmutableList([
{ 'count': 2, 'me': false, 'name': '👍', 'url': undefined },
{ 'count': 2, 'me': false, 'name': '❤', 'url': undefined },
]) as ImmutableList<ImmutableMap<string, any>>;
].map((react) => emojiReactionSchema.parse(react)));
expect(simulateEmojiReact(emojiReacts, 'soapbox', 'https://gleasonator.com/emoji/Gleasonator/soapbox.png')).toEqual(fromJS([
{ 'count': 2, 'me': false, 'name': '👍', 'url': undefined },
{ 'count': 2, 'me': false, 'name': '❤', 'url': undefined },
@@ -209,10 +193,10 @@ describe('simulateEmojiReact', () => {
describe('simulateUnEmojiReact', () => {
it('removes the emoji from the list', () => {
const emojiReacts = fromJS([
const emojiReacts = ImmutableList([
{ 'count': 2, 'me': false, 'name': '👍' },
{ 'count': 3, 'me': true, 'name': '❤' },
]) as ImmutableList<ImmutableMap<string, any>>;
].map((react) => emojiReactionSchema.parse(react)));
expect(simulateUnEmojiReact(emojiReacts, '❤')).toEqual(fromJS([
{ 'count': 2, 'me': false, 'name': '👍' },
{ 'count': 2, 'me': false, 'name': '❤' },
@@ -220,11 +204,11 @@ describe('simulateUnEmojiReact', () => {
});
it('removes the emoji if it\'s the last one in the list', () => {
const emojiReacts = fromJS([
const emojiReacts = ImmutableList([
{ 'count': 2, 'me': false, 'name': '👍' },
{ 'count': 2, 'me': false, 'name': '❤' },
{ 'count': 1, 'me': true, 'name': '😯' },
]) as ImmutableList<ImmutableMap<string, any>>;
].map((react) => emojiReactionSchema.parse(react)));
expect(simulateUnEmojiReact(emojiReacts, '😯')).toEqual(fromJS([
{ 'count': 2, 'me': false, 'name': '👍' },
{ 'count': 2, 'me': false, 'name': '❤' },
@@ -232,11 +216,11 @@ describe('simulateUnEmojiReact', () => {
});
it ('removes custom emoji from the list', () => {
const emojiReacts = fromJS([
const emojiReacts = ImmutableList([
{ 'count': 2, 'me': false, 'name': '👍' },
{ 'count': 2, 'me': false, 'name': '❤' },
{ 'count': 1, 'me': true, 'name': 'soapbox', 'url': 'https://gleasonator.com/emoji/Gleasonator/soapbox.png' },
]) as ImmutableList<ImmutableMap<string, any>>;
].map((react) => emojiReactionSchema.parse(react)));
expect(simulateUnEmojiReact(emojiReacts, 'soapbox')).toEqual(fromJS([
{ 'count': 2, 'me': false, 'name': '👍' },
{ 'count': 2, 'me': false, 'name': '❤' },

View File

@@ -1,9 +1,6 @@
import {
Map as ImmutableMap,
List as ImmutableList,
} from 'immutable';
import { List as ImmutableList } from 'immutable';
import type { Me } from 'soapbox/types/soapbox';
import { EmojiReaction, emojiReactionSchema } from 'soapbox/schemas';
// https://emojipedia.org/facebook
// I've customized them.
@@ -16,18 +13,16 @@ export const ALLOWED_EMOJI = ImmutableList([
'😩',
]);
type Account = ImmutableMap<string, any>;
type EmojiReact = ImmutableMap<string, any>;
export const sortEmoji = (emojiReacts: ImmutableList<EmojiReact>, allowedEmoji: ImmutableList<string>): ImmutableList<EmojiReact> => (
export const sortEmoji = (emojiReacts: ImmutableList<EmojiReaction>, allowedEmoji: ImmutableList<string>): ImmutableList<EmojiReaction> => (
emojiReacts
.sortBy(emojiReact =>
-(emojiReact.get('count') + Number(allowedEmoji.includes(emojiReact.get('name')))))
-((emojiReact.count || 0) + Number(allowedEmoji.includes(emojiReact.name))))
);
export const mergeEmojiFavourites = (emojiReacts = ImmutableList<EmojiReact>(), favouritesCount: number, favourited: boolean) => {
export const mergeEmojiFavourites = (emojiReacts: ImmutableList<EmojiReaction> | null, favouritesCount: number, favourited: boolean) => {
if (!emojiReacts) return ImmutableList([emojiReactionSchema.parse({ count: favouritesCount, me: favourited, name: '👍' })]);
if (!favouritesCount) return emojiReacts;
const likeIndex = emojiReacts.findIndex(emojiReact => emojiReact.get('name') === '👍');
const likeIndex = emojiReacts.findIndex(emojiReact => emojiReact.name === '👍');
if (likeIndex > -1) {
const likeCount = Number(emojiReacts.getIn([likeIndex, 'count']));
favourited = favourited || Boolean(emojiReacts.getIn([likeIndex, 'me'], false));
@@ -35,69 +30,43 @@ export const mergeEmojiFavourites = (emojiReacts = ImmutableList<EmojiReact>(),
.setIn([likeIndex, 'count'], likeCount + favouritesCount)
.setIn([likeIndex, 'me'], favourited);
} else {
return emojiReacts.push(ImmutableMap({ count: favouritesCount, me: favourited, name: '👍' }));
return emojiReacts.push(emojiReactionSchema.parse({ count: favouritesCount, me: favourited, name: '👍' }));
}
};
const hasMultiReactions = (emojiReacts: ImmutableList<EmojiReact>, account: Account): boolean => (
emojiReacts.filter(
e => e.get('accounts').filter(
(a: Account) => a.get('id') === account.get('id'),
).count() > 0,
).count() > 1
);
const inAccounts = (accounts: ImmutableList<Account>, id: string): boolean => (
accounts.filter(a => a.get('id') === id).count() > 0
);
export const oneEmojiPerAccount = (emojiReacts: ImmutableList<EmojiReact>, me: Me) => {
emojiReacts = emojiReacts.reverse();
return emojiReacts.reduce((acc, cur, idx) => {
const accounts = cur.get('accounts', ImmutableList())
.filter((a: Account) => !hasMultiReactions(acc, a));
return acc.set(idx, cur.merge({
accounts: accounts,
count: accounts.count(),
me: me ? inAccounts(accounts, me) : false,
}));
}, emojiReacts)
.filter(e => e.get('count') > 0)
.reverse();
};
export const reduceEmoji = (emojiReacts: ImmutableList<EmojiReact>, favouritesCount: number, favourited: boolean, allowedEmoji = ALLOWED_EMOJI): ImmutableList<EmojiReact> => (
export const reduceEmoji = (emojiReacts: ImmutableList<EmojiReaction> | null, favouritesCount: number, favourited: boolean, allowedEmoji = ALLOWED_EMOJI): ImmutableList<EmojiReaction> => (
sortEmoji(
mergeEmojiFavourites(emojiReacts, favouritesCount, favourited),
allowedEmoji,
));
export const getReactForStatus = (status: any, allowedEmoji = ALLOWED_EMOJI): EmojiReact | undefined => {
export const getReactForStatus = (status: any, allowedEmoji = ALLOWED_EMOJI): EmojiReaction | undefined => {
if (!status.reactions) return;
const result = reduceEmoji(
status.pleroma.get('emoji_reactions', ImmutableList()),
status.reactions,
status.favourites_count || 0,
status.favourited,
allowedEmoji,
).filter(e => e.get('me') === true)
).filter(e => e.me === true)
.get(0);
return typeof result?.get('name') === 'string' ? result : undefined;
return typeof result?.name === 'string' ? result : undefined;
};
export const simulateEmojiReact = (emojiReacts: ImmutableList<EmojiReact>, emoji: string, url?: string) => {
const idx = emojiReacts.findIndex(e => e.get('name') === emoji);
export const simulateEmojiReact = (emojiReacts: ImmutableList<EmojiReaction>, emoji: string, url?: string) => {
const idx = emojiReacts.findIndex(e => e.name === emoji);
const emojiReact = emojiReacts.get(idx);
if (idx > -1 && emojiReact) {
return emojiReacts.set(idx, emojiReact.merge({
count: emojiReact.get('count') + 1,
return emojiReacts.set(idx, emojiReactionSchema.parse({
...emojiReact,
count: (emojiReact.count || 0) + 1,
me: true,
url,
}));
} else {
return emojiReacts.push(ImmutableMap({
return emojiReacts.push(emojiReactionSchema.parse({
count: 1,
me: true,
name: emoji,
@@ -106,17 +75,18 @@ export const simulateEmojiReact = (emojiReacts: ImmutableList<EmojiReact>, emoji
}
};
export const simulateUnEmojiReact = (emojiReacts: ImmutableList<EmojiReact>, emoji: string) => {
export const simulateUnEmojiReact = (emojiReacts: ImmutableList<EmojiReaction>, emoji: string) => {
const idx = emojiReacts.findIndex(e =>
e.get('name') === emoji && e.get('me') === true);
e.name === emoji && e.me === true);
const emojiReact = emojiReacts.get(idx);
if (emojiReact) {
const newCount = emojiReact.get('count') - 1;
const newCount = (emojiReact.count || 1) - 1;
if (newCount < 1) return emojiReacts.delete(idx);
return emojiReacts.set(idx, emojiReact.merge({
count: emojiReact.get('count') - 1,
return emojiReacts.set(idx, emojiReactionSchema.parse({
...emojiReact,
count: (emojiReact.count || 1) - 1,
me: false,
}));
} else {

View File

@@ -200,4 +200,11 @@ const httpErrorMessages: { code: number; name: string; description: string }[] =
},
];
export { buildErrorMessage, httpErrorMessages };
/** Whether the error is caused by a JS chunk failing to load. */
function isNetworkError(error: unknown): boolean {
return error instanceof Error
&& error.name === 'TypeError'
&& error.message.startsWith('Failed to fetch dynamically imported module: ');
}
export { buildErrorMessage, httpErrorMessages, isNetworkError };

View File

@@ -14,24 +14,30 @@ const overrides = custom('features');
/** Truthy array convenience function */
const any = (arr: Array<any>): boolean => arr.some(Boolean);
/**
* Firefish, a fork of Misskey. Formerly known as Calckey.
* @see {@link https://joinfirefish.org/}
*/
export const FIREFISH = 'Firefish';
/**
* Ditto, a Nostr server with Mastodon API.
* @see {@link https://gitlab.com/soapbox-pub/ditto}
*/
export const DITTO = 'Ditto';
/**
* Firefish, a fork of Misskey. Formerly known as Calckey.
* @see {@link https://joinfirefish.org/}
*/
export const FIREFISH = 'Firefish';
/**
* Friendica, decentralized social platform implementing multiple federation protocols.
* @see {@link https://friendi.ca/}
*/
export const FRIENDICA = 'Friendica';
/**
* Iceshrimp, yet another Misskey fork.
* @see {@link https://iceshrimp.dev/}
*/
export const ICESHRIMP = 'Iceshrimp';
/**
* Mastodon, the software upon which this is all based.
* @see {@link https://joinmastodon.org/}
@@ -143,6 +149,7 @@ const getInstanceFeatures = (instance: Instance) => {
*/
accountLookup: any([
v.software === FIREFISH,
v.software === ICESHRIMP,
v.software === MASTODON && gte(v.compatVersion, '3.4.0'),
v.software === PLEROMA && gte(v.version, '2.4.50'),
v.software === TAKAHE && gte(v.version, '0.6.1'),
@@ -192,6 +199,7 @@ const getInstanceFeatures = (instance: Instance) => {
* @see {@link https://docs.joinmastodon.org/methods/announcements/}
*/
announcements: any([
v.software === ICESHRIMP,
v.software === MASTODON && gte(v.compatVersion, '3.1.0'),
v.software === PLEROMA && gte(v.version, '2.2.49'),
v.software === TAKAHE && gte(v.version, '0.7.0'),
@@ -230,11 +238,13 @@ const getInstanceFeatures = (instance: Instance) => {
*/
bookmarks: any([
v.software === FIREFISH,
v.software === ICESHRIMP,
v.software === FRIENDICA,
v.software === MASTODON && gte(v.compatVersion, '3.1.0'),
v.software === PLEROMA && gte(v.version, '0.9.9'),
v.software === PIXELFED,
v.software === TAKAHE && gte(v.version, '0.9.0'),
v.software === DITTO,
]),
/**
@@ -319,6 +329,7 @@ const getInstanceFeatures = (instance: Instance) => {
*/
conversations: any([
v.software === FIREFISH,
v.software === ICESHRIMP,
v.software === FRIENDICA,
v.software === MASTODON && gte(v.compatVersion, '2.6.0'),
v.software === PLEROMA && gte(v.version, '0.9.9'),
@@ -359,6 +370,7 @@ const getInstanceFeatures = (instance: Instance) => {
editProfile: any([
v.software === FIREFISH,
v.software === FRIENDICA,
v.software === ICESHRIMP,
v.software === MASTODON,
v.software === MITRA,
v.software === PIXELFED,
@@ -374,6 +386,7 @@ const getInstanceFeatures = (instance: Instance) => {
*/
editStatuses: any([
v.software === FRIENDICA && gte(v.version, '2022.12.0'),
v.software === ICESHRIMP,
v.software === MASTODON && gte(v.version, '3.5.0'),
v.software === TAKAHE && gte(v.version, '0.8.0'),
features.includes('editing'),
@@ -406,6 +419,13 @@ const getInstanceFeatures = (instance: Instance) => {
*/
emojiReacts: v.software === PLEROMA && gte(v.version, '2.0.0'),
/**
* Ability to add emoji reactions to a status available in Mastodon forks.
* @see POST /v1/statuses/:id/react/:emoji
* @see POST /v1/statuses/:id/unreact/:emoji
*/
emojiReactsMastodon: instance.configuration.reactions.max_reactions > 0,
/**
* The backend allows only non-RGI ("Recommended for General Interchange") emoji reactions.
* @see PUT /api/v1/pleroma/statuses/:id/reactions/:emoji
@@ -444,6 +464,7 @@ const getInstanceFeatures = (instance: Instance) => {
exposableReactions: any([
v.software === FIREFISH,
v.software === FRIENDICA,
v.software === ICESHRIMP,
v.software === MASTODON,
v.software === TAKAHE && gte(v.version, '0.6.1'),
v.software === TRUTHSOCIAL,
@@ -638,6 +659,7 @@ const getInstanceFeatures = (instance: Instance) => {
lists: any([
v.software === FIREFISH,
v.software === FRIENDICA,
v.software === ICESHRIMP,
v.software === MASTODON && gte(v.compatVersion, '2.1.0'),
v.software === PLEROMA && gte(v.version, '0.9.9'),
]),
@@ -693,6 +715,7 @@ const getInstanceFeatures = (instance: Instance) => {
* @see PUT /api/v1/accounts/:id/mute
*/
mutesDuration: any([
v.software === ICESHRIMP,
v.software === PLEROMA && gte(v.version, '2.3.0'),
v.software === MASTODON && gte(v.compatVersion, '3.3.0'),
v.software === TAKAHE,
@@ -725,6 +748,7 @@ const getInstanceFeatures = (instance: Instance) => {
* @see GET /api/v1/notifications
*/
notificationsIncludeTypes: any([
v.software === ICESHRIMP,
v.software === MASTODON && gte(v.compatVersion, '3.5.0'),
v.software === PLEROMA && gte(v.version, '2.4.50'),
v.software === TAKAHE && gte(v.version, '0.6.2'),
@@ -749,6 +773,7 @@ const getInstanceFeatures = (instance: Instance) => {
*/
polls: any([
v.software === FIREFISH,
v.software === ICESHRIMP,
v.software === MASTODON && gte(v.version, '2.8.0'),
v.software === PLEROMA,
v.software === TAKAHE && gte(v.version, '0.8.0'),
@@ -789,6 +814,7 @@ const getInstanceFeatures = (instance: Instance) => {
publicTimeline: any([
v.software === FIREFISH,
v.software === FRIENDICA,
v.software === ICESHRIMP,
v.software === MASTODON,
v.software === PLEROMA,
v.software === TAKAHE,
@@ -874,6 +900,7 @@ const getInstanceFeatures = (instance: Instance) => {
* @see POST /api/v2/search
*/
searchFromAccount: any([
v.software === ICESHRIMP,
v.software === MASTODON && gte(v.version, '2.8.0'),
v.software === PLEROMA && gte(v.version, '1.0.0'),
]),
@@ -927,6 +954,7 @@ const getInstanceFeatures = (instance: Instance) => {
*/
suggestionsV2: any([
v.software === FRIENDICA,
v.software === ICESHRIMP,
v.software === MASTODON && gte(v.compatVersion, '3.4.0'),
v.software === TRUTHSOCIAL,
features.includes('v2_suggestions'),
@@ -943,6 +971,7 @@ const getInstanceFeatures = (instance: Instance) => {
* @see GET /api/v1/trends/statuses
*/
trendingStatuses: any([
v.software === ICESHRIMP,
v.software === FRIENDICA && gte(v.version, '2022.12.0'),
v.software === MASTODON && gte(v.compatVersion, '3.5.0'),
]),
@@ -953,6 +982,7 @@ const getInstanceFeatures = (instance: Instance) => {
*/
trends: any([
v.software === FRIENDICA && gte(v.version, '2022.12.0'),
v.software === ICESHRIMP,
v.software === MASTODON && gte(v.compatVersion, '3.0.0'),
v.software === TRUTHSOCIAL,
v.software === DITTO,

View File

@@ -1,9 +1,16 @@
/** Register the ServiceWorker. */
function registerSW(path: string) {
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register(path, { scope: '/' });
});
navigator.serviceWorker.register(path, { scope: '/' });
}
}
/** Prevent a new ServiceWorker from being installed. */
function lockSW() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register = () => {
throw new Error('ServiceWorker already registered.');
};
}
}
@@ -22,4 +29,5 @@ const unregisterSW = async(): Promise<void> => {
export {
registerSW,
unregisterSW,
lockSW,
};