sforkowany z mirror/soapbox
Merge remote-tracking branch 'soapbox/next' into next_
commit
267ab4b153
|
@ -94,6 +94,12 @@ module.exports = {
|
||||||
'no-loop-func': 'error',
|
'no-loop-func': 'error',
|
||||||
'no-mixed-spaces-and-tabs': 'error',
|
'no-mixed-spaces-and-tabs': 'error',
|
||||||
'no-nested-ternary': 'warn',
|
'no-nested-ternary': 'warn',
|
||||||
|
'no-restricted-imports': ['error', {
|
||||||
|
patterns: [{
|
||||||
|
group: ['react-inlinesvg'],
|
||||||
|
message: 'Use the SvgIcon component instead.',
|
||||||
|
}],
|
||||||
|
}],
|
||||||
'no-trailing-spaces': 'warn',
|
'no-trailing-spaces': 'warn',
|
||||||
'no-undef': 'error',
|
'no-undef': 'error',
|
||||||
'no-unreachable': 'error',
|
'no-unreachable': 'error',
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
{
|
||||||
|
"uri": "pixelfed.social",
|
||||||
|
"title": "pixelfed",
|
||||||
|
"short_description": "Pixelfed is an image sharing platform, an ethical alternative to centralized platforms",
|
||||||
|
"description": "Pixelfed is an image sharing platform, an ethical alternative to centralized platforms",
|
||||||
|
"email": "hello@pixelfed.org",
|
||||||
|
"version": "2.7.2 (compatible; Pixelfed 0.11.2)",
|
||||||
|
"urls": {
|
||||||
|
"streaming_api": "wss://pixelfed.social"
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"user_count": 45061,
|
||||||
|
"status_count": 301357,
|
||||||
|
"domain_count": 5028
|
||||||
|
},
|
||||||
|
"thumbnail": "https://pixelfed.social/img/pixelfed-icon-color.png",
|
||||||
|
"languages": [
|
||||||
|
"en"
|
||||||
|
],
|
||||||
|
"registrations": true,
|
||||||
|
"approval_required": false,
|
||||||
|
"contact_account": {
|
||||||
|
"id": "1",
|
||||||
|
"username": "admin",
|
||||||
|
"acct": "admin",
|
||||||
|
"display_name": "Admin",
|
||||||
|
"discoverable": true,
|
||||||
|
"locked": false,
|
||||||
|
"followers_count": 419,
|
||||||
|
"following_count": 2,
|
||||||
|
"statuses_count": 6,
|
||||||
|
"note": "pixelfed.social Admin. Managed by @dansup",
|
||||||
|
"url": "https://pixelfed.social/admin",
|
||||||
|
"avatar": "https://pixelfed.social/storage/avatars/000/000/000/001/LSHNCgwbby7wu3iCYV6H_avatar.png?v=4",
|
||||||
|
"created_at": "2018-06-01T03:54:08.000000Z",
|
||||||
|
"avatar_static": "https://pixelfed.social/storage/avatars/000/000/000/001/LSHNCgwbby7wu3iCYV6H_avatar.png?v=4",
|
||||||
|
"bot": false,
|
||||||
|
"emojis": [],
|
||||||
|
"fields": [],
|
||||||
|
"header": "https://pixelfed.social/storage/headers/missing.png",
|
||||||
|
"header_static": "https://pixelfed.social/storage/headers/missing.png",
|
||||||
|
"last_status_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 United States"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { rootState } from '../../jest/test-helpers';
|
||||||
|
import { getSoapboxConfig } from '../soapbox';
|
||||||
|
|
||||||
|
const ASCII_HEART = '❤'; // '\u2764\uFE0F'
|
||||||
|
const RED_HEART_RGI = '❤️'; // '\u2764'
|
||||||
|
|
||||||
|
describe('getSoapboxConfig()', () => {
|
||||||
|
it('returns RGI heart on Pleroma > 2.3', () => {
|
||||||
|
const state = rootState.setIn(['instance', 'version'], '2.7.2 (compatible; Pleroma 2.3.0)');
|
||||||
|
expect(getSoapboxConfig(state).allowedEmoji.includes(RED_HEART_RGI)).toBe(true);
|
||||||
|
expect(getSoapboxConfig(state).allowedEmoji.includes(ASCII_HEART)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an ASCII heart on Pleroma < 2.3', () => {
|
||||||
|
const state = rootState.setIn(['instance', 'version'], '2.7.2 (compatible; Pleroma 2.0.0)');
|
||||||
|
expect(getSoapboxConfig(state).allowedEmoji.includes(ASCII_HEART)).toBe(true);
|
||||||
|
expect(getSoapboxConfig(state).allowedEmoji.includes(RED_HEART_RGI)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,9 +1,9 @@
|
||||||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
import { getHost } from 'soapbox/actions/instance';
|
import { getHost } from 'soapbox/actions/instance';
|
||||||
import { normalizeSoapboxConfig } from 'soapbox/normalizers';
|
import { normalizeSoapboxConfig } from 'soapbox/normalizers';
|
||||||
import KVStore from 'soapbox/storage/kv_store';
|
import KVStore from 'soapbox/storage/kv_store';
|
||||||
|
import { removeVS16s } from 'soapbox/utils/emoji';
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
|
|
||||||
import api, { staticClient } from '../api';
|
import api, { staticClient } from '../api';
|
||||||
|
@ -15,38 +15,24 @@ export const SOAPBOX_CONFIG_REMEMBER_REQUEST = 'SOAPBOX_CONFIG_REMEMBER_REQUEST'
|
||||||
export const SOAPBOX_CONFIG_REMEMBER_SUCCESS = 'SOAPBOX_CONFIG_REMEMBER_SUCCESS';
|
export const SOAPBOX_CONFIG_REMEMBER_SUCCESS = 'SOAPBOX_CONFIG_REMEMBER_SUCCESS';
|
||||||
export const SOAPBOX_CONFIG_REMEMBER_FAIL = 'SOAPBOX_CONFIG_REMEMBER_FAIL';
|
export const SOAPBOX_CONFIG_REMEMBER_FAIL = 'SOAPBOX_CONFIG_REMEMBER_FAIL';
|
||||||
|
|
||||||
const allowedEmoji = ImmutableList([
|
|
||||||
'👍',
|
|
||||||
'❤',
|
|
||||||
'😆',
|
|
||||||
'😮',
|
|
||||||
'😢',
|
|
||||||
'😩',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// https://git.pleroma.social/pleroma/pleroma/-/issues/2355
|
|
||||||
const allowedEmojiRGI = ImmutableList([
|
|
||||||
'👍',
|
|
||||||
'❤️',
|
|
||||||
'😆',
|
|
||||||
'😮',
|
|
||||||
'😢',
|
|
||||||
'😩',
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const makeDefaultConfig = features => {
|
|
||||||
return ImmutableMap({
|
|
||||||
allowedEmoji: features.emojiReactsRGI ? allowedEmojiRGI : allowedEmoji,
|
|
||||||
displayFqn: Boolean(features.federating),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getSoapboxConfig = createSelector([
|
export const getSoapboxConfig = createSelector([
|
||||||
state => state.get('soapbox'),
|
state => state.soapbox,
|
||||||
state => getFeatures(state.get('instance')),
|
state => getFeatures(state.instance),
|
||||||
], (soapbox, features) => {
|
], (soapbox, features) => {
|
||||||
const defaultConfig = makeDefaultConfig(features);
|
// Do some additional normalization with the state
|
||||||
return normalizeSoapboxConfig(soapbox).merge(defaultConfig);
|
return normalizeSoapboxConfig(soapbox).withMutations(soapboxConfig => {
|
||||||
|
|
||||||
|
// If displayFqn isn't set, infer it from federation
|
||||||
|
if (soapbox.get('displayFqn') === undefined) {
|
||||||
|
soapboxConfig.set('displayFqn', features.federating);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If RGI reacts aren't supported, strip VS16s
|
||||||
|
// // https://git.pleroma.social/pleroma/pleroma/-/issues/2355
|
||||||
|
if (!features.emojiReactsRGI) {
|
||||||
|
soapboxConfig.set('allowedEmoji', soapboxConfig.allowedEmoji.map(removeVS16s));
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export function rememberSoapboxConfig(host) {
|
export function rememberSoapboxConfig(host) {
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
export default function InlineSVG({ src }) {
|
|
||||||
return <svg id={src} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
InlineSVG.propTypes = {
|
|
||||||
src: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
|
|
||||||
};
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
interface IInlineSVG {
|
||||||
|
loader?: JSX.Element,
|
||||||
|
}
|
||||||
|
|
||||||
|
const InlineSVG: React.FC<IInlineSVG> = ({ loader }): JSX.Element => {
|
||||||
|
if (loader) {
|
||||||
|
return loader;
|
||||||
|
} else {
|
||||||
|
throw 'You used react-inlinesvg without a loader! This will cause jumpy loading during render.';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InlineSVG;
|
|
@ -82,7 +82,7 @@ class BirthdayReminders extends ImmutablePureComponent {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (birthdays.size === 1) {
|
if (birthdays.size === 1) {
|
||||||
return <FormattedMessage id='notification.birthday' defaultMessage='{name} has birthday today' values={{ name: link }} />;
|
return <FormattedMessage id='notification.birthday' defaultMessage='{name} has a birthday today' values={{ name: link }} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -109,7 +109,7 @@ class BirthdayReminders extends ImmutablePureComponent {
|
||||||
const { intl, birthdays, account } = this.props;
|
const { intl, birthdays, account } = this.props;
|
||||||
|
|
||||||
if (birthdays.size === 1) {
|
if (birthdays.size === 1) {
|
||||||
return intl.formatMessage({ id: 'notification.birthday', defaultMessage: '{name} has birthday today' }, { name: account.get('display_name') });
|
return intl.formatMessage({ id: 'notification.birthday', defaultMessage: '{name} has a birthday today' }, { name: account.get('display_name') });
|
||||||
}
|
}
|
||||||
|
|
||||||
return intl.formatMessage(
|
return intl.formatMessage(
|
||||||
|
|
|
@ -37,16 +37,14 @@ const SidebarNavigationLink = ({ icon, text, to, count }: ISidebarNavigationLink
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div className='h-5 w-5'>
|
<Icon
|
||||||
<Icon
|
src={icon}
|
||||||
src={icon}
|
className={classNames({
|
||||||
className={classNames({
|
'h-5 w-5': true,
|
||||||
'h-full w-full': true,
|
'text-primary-700 dark:text-white': !isActive,
|
||||||
'text-primary-700 dark:text-white': !isActive,
|
'text-white': isActive,
|
||||||
'text-white': isActive,
|
})}
|
||||||
})}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<Text weight='semibold' theme='inherit'>{text}</Text>
|
<Text weight='semibold' theme='inherit'>{text}</Text>
|
||||||
|
|
|
@ -33,7 +33,7 @@ const SidebarNavigation = () => {
|
||||||
{account && (
|
{account && (
|
||||||
<>
|
<>
|
||||||
<SidebarNavigationLink
|
<SidebarNavigationLink
|
||||||
to={`/@${account.get('acct')}`}
|
to={`/@${account.acct}`}
|
||||||
icon={require('icons/user.svg')}
|
icon={require('icons/user.svg')}
|
||||||
text={<FormattedMessage id='tabs_bar.profile' defaultMessage='Profile' />}
|
text={<FormattedMessage id='tabs_bar.profile' defaultMessage='Profile' />}
|
||||||
/>
|
/>
|
||||||
|
@ -79,7 +79,7 @@ const SidebarNavigation = () => {
|
||||||
/>
|
/>
|
||||||
)} */}
|
)} */}
|
||||||
|
|
||||||
{(account && instance.get('invites_enabled')) && (
|
{(account && instance.invites_enabled) && (
|
||||||
<SidebarNavigationLink
|
<SidebarNavigationLink
|
||||||
to={`${baseURL}/invites`}
|
to={`${baseURL}/invites`}
|
||||||
icon={require('@tabler/icons/icons/mailbox.svg')}
|
icon={require('@tabler/icons/icons/mailbox.svg')}
|
||||||
|
@ -101,7 +101,7 @@ const SidebarNavigation = () => {
|
||||||
src={require('@tabler/icons/icons/users.svg')}
|
src={require('@tabler/icons/icons/users.svg')}
|
||||||
className={classNames('primary-navigation__icon', { 'svg-icon--active': location.pathname === '/timeline/local' })}
|
className={classNames('primary-navigation__icon', { 'svg-icon--active': location.pathname === '/timeline/local' })}
|
||||||
/>
|
/>
|
||||||
{instance.get('title')}
|
{instance.title}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
) : (
|
) : (
|
||||||
<NavLink to='/timeline/local' className='btn grouped'>
|
<NavLink to='/timeline/local' className='btn grouped'>
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import InlineSVG from 'react-inlinesvg';
|
|
||||||
|
|
||||||
import { Text } from 'soapbox/components/ui';
|
import { Text, Icon } from 'soapbox/components/ui';
|
||||||
import { shortNumberFormat } from 'soapbox/utils/numbers';
|
import { shortNumberFormat } from 'soapbox/utils/numbers';
|
||||||
|
|
||||||
const COLORS = {
|
const COLORS = {
|
||||||
|
@ -53,10 +52,10 @@ const StatusActionButton = React.forwardRef((props: IStatusActionButton, ref: Re
|
||||||
)}
|
)}
|
||||||
{...filteredProps}
|
{...filteredProps}
|
||||||
>
|
>
|
||||||
<InlineSVG
|
<Icon
|
||||||
src={icon}
|
src={icon}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'p-1 rounded-full box-content',
|
'rounded-full',
|
||||||
'group-focus:outline-none group-focus:ring-2 group-focus:ring-offset-2 dark:ring-offset-0 group-focus:ring-primary-500',
|
'group-focus:outline-none group-focus:ring-2 group-focus:ring-offset-2 dark:ring-offset-0 group-focus:ring-primary-500',
|
||||||
{
|
{
|
||||||
'fill-accent-300 hover:fill-accent-300': active && filled && color === COLORS.accent,
|
'fill-accent-300 hover:fill-accent-300': active && filled && color === COLORS.accent,
|
||||||
|
|
|
@ -578,9 +578,10 @@ class StatusActionBar extends ImmutablePureComponent<IStatusActionBar, IStatusAc
|
||||||
'😮': messages.reactionOpenMouth,
|
'😮': messages.reactionOpenMouth,
|
||||||
'😢': messages.reactionCry,
|
'😢': messages.reactionCry,
|
||||||
'😩': messages.reactionWeary,
|
'😩': messages.reactionWeary,
|
||||||
|
'': messages.favourite,
|
||||||
};
|
};
|
||||||
|
|
||||||
const meEmojiTitle = intl.formatMessage(meEmojiReact ? reactMessages[meEmojiReact] : messages.favourite);
|
const meEmojiTitle = intl.formatMessage(reactMessages[meEmojiReact || ''] || messages.favourite);
|
||||||
|
|
||||||
const menu = this._makeMenu(publicStatus);
|
const menu = this._makeMenu(publicStatus);
|
||||||
let reblogIcon = require('@tabler/icons/icons/repeat.svg');
|
let reblogIcon = require('@tabler/icons/icons/repeat.svg');
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import InlineSVG from 'react-inlinesvg';
|
import InlineSVG from 'react-inlinesvg'; // eslint-disable-line no-restricted-imports
|
||||||
|
|
||||||
export default class SvgIcon extends React.PureComponent {
|
export default class SvgIcon extends React.PureComponent {
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ export default class SvgIcon extends React.PureComponent {
|
||||||
className={classNames('svg-icon', className)}
|
className={classNames('svg-icon', className)}
|
||||||
{...other}
|
{...other}
|
||||||
>
|
>
|
||||||
<InlineSVG src={src} title={alt} />
|
<InlineSVG src={src} title={alt} loader={<></>} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import InlineSVG from 'react-inlinesvg';
|
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { Text } from 'soapbox/components/ui';
|
import { Text } from 'soapbox/components/ui';
|
||||||
|
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
|
||||||
|
|
||||||
const sizes = {
|
const sizes = {
|
||||||
md: 'p-4 sm:rounded-xl',
|
md: 'p-4 sm:rounded-xl',
|
||||||
|
@ -54,7 +54,7 @@ const CardHeader: React.FC<ICardHeader> = ({ children, backHref, onBackClick }):
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp {...backAttributes} className='mr-2 text-gray-900 dark:text-gray-100' aria-label={intl.formatMessage(messages.back)}>
|
<Comp {...backAttributes} className='mr-2 text-gray-900 dark:text-gray-100' aria-label={intl.formatMessage(messages.back)}>
|
||||||
<InlineSVG src={require('@tabler/icons/icons/arrow-left.svg')} className='h-6 w-6' />
|
<SvgIcon src={require('@tabler/icons/icons/arrow-left.svg')} className='h-6 w-6' />
|
||||||
<span className='sr-only' data-testid='back-button'>Back</span>
|
<span className='sr-only' data-testid='back-button'>Back</span>
|
||||||
</Comp>
|
</Comp>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,34 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { removeVS16s, toCodePoints } from 'soapbox/utils/emoji';
|
||||||
import { joinPublicPath } from 'soapbox/utils/static';
|
import { joinPublicPath } from 'soapbox/utils/static';
|
||||||
|
|
||||||
// Taken from twemoji-parser
|
|
||||||
// https://github.com/twitter/twemoji-parser/blob/a97ef3994e4b88316812926844d51c296e889f76/src/index.js
|
|
||||||
const removeVS16s = (rawEmoji: string): string => {
|
|
||||||
const vs16RegExp = /\uFE0F/g;
|
|
||||||
const zeroWidthJoiner = String.fromCharCode(0x200d);
|
|
||||||
return rawEmoji.indexOf(zeroWidthJoiner) < 0 ? rawEmoji.replace(vs16RegExp, '') : rawEmoji;
|
|
||||||
};
|
|
||||||
|
|
||||||
const toCodePoints = (unicodeSurrogates: string): string[] => {
|
|
||||||
const points = [];
|
|
||||||
let char = 0;
|
|
||||||
let previous = 0;
|
|
||||||
let i = 0;
|
|
||||||
while (i < unicodeSurrogates.length) {
|
|
||||||
char = unicodeSurrogates.charCodeAt(i++);
|
|
||||||
if (previous) {
|
|
||||||
points.push((0x10000 + ((previous - 0xd800) << 10) + (char - 0xdc00)).toString(16));
|
|
||||||
previous = 0;
|
|
||||||
} else if (char > 0xd800 && char <= 0xdbff) {
|
|
||||||
previous = char;
|
|
||||||
} else {
|
|
||||||
points.push(char.toString(16));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return points;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IEmoji extends React.ImgHTMLAttributes<HTMLImageElement> {
|
interface IEmoji extends React.ImgHTMLAttributes<HTMLImageElement> {
|
||||||
emoji: string,
|
emoji: string,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import InlineSVG from 'react-inlinesvg';
|
|
||||||
|
|
||||||
|
import SvgIcon from '../icon/svg-icon';
|
||||||
import Text from '../text/text';
|
import Text from '../text/text';
|
||||||
|
|
||||||
interface IIconButton extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
interface IIconButton extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
|
@ -24,7 +24,7 @@ const IconButton = React.forwardRef((props: IIconButton, ref: React.ForwardedRef
|
||||||
{...filteredProps}
|
{...filteredProps}
|
||||||
data-testid='icon-button'
|
data-testid='icon-button'
|
||||||
>
|
>
|
||||||
<InlineSVG src={src} className={iconClassName} />
|
<SvgIcon src={src} className={iconClassName} />
|
||||||
|
|
||||||
{text ? (
|
{text ? (
|
||||||
<Text tag='span' theme='muted' size='sm'>
|
<Text tag='span' theme='muted' size='sm'>
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { render, screen } from '../../../../jest/test-helpers';
|
||||||
|
import SvgIcon from '../svg-icon';
|
||||||
|
|
||||||
|
describe('<SvgIcon />', () => {
|
||||||
|
it('renders loading element with default size', () => {
|
||||||
|
render(<SvgIcon className='text-primary-500' src={require('@tabler/icons/icons/code.svg')} />);
|
||||||
|
|
||||||
|
const svg = screen.getByTestId('svg-icon-loader');
|
||||||
|
expect(svg.getAttribute('width')).toBe('24');
|
||||||
|
expect(svg.getAttribute('height')).toBe('24');
|
||||||
|
expect(svg.getAttribute('class')).toBe('text-primary-500');
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,14 +1,16 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import InlineSVG from 'react-inlinesvg';
|
|
||||||
|
import SvgIcon from './svg-icon';
|
||||||
|
|
||||||
interface IIcon {
|
interface IIcon {
|
||||||
className?: string,
|
className?: string,
|
||||||
count?: number,
|
count?: number,
|
||||||
alt?: string,
|
alt?: string,
|
||||||
src: string,
|
src: string,
|
||||||
|
size?: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
const Icon = ({ src, alt, count, ...filteredProps }: IIcon): JSX.Element => (
|
const Icon = ({ src, alt, count, size, ...filteredProps }: IIcon): JSX.Element => (
|
||||||
<div className='relative' data-testid='icon'>
|
<div className='relative' data-testid='icon'>
|
||||||
{count ? (
|
{count ? (
|
||||||
<span className='absolute -top-2 -right-3 block px-1.5 py-0.5 bg-accent-500 text-xs text-white rounded-full ring-2 ring-white'>
|
<span className='absolute -top-2 -right-3 block px-1.5 py-0.5 bg-accent-500 text-xs text-white rounded-full ring-2 ring-white'>
|
||||||
|
@ -16,7 +18,7 @@ const Icon = ({ src, alt, count, ...filteredProps }: IIcon): JSX.Element => (
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<InlineSVG src={src} title={alt} {...filteredProps} />
|
<SvgIcon src={src} size={size} alt={alt} {...filteredProps} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React from 'react';
|
||||||
|
import InlineSVG from 'react-inlinesvg'; // eslint-disable-line no-restricted-imports
|
||||||
|
|
||||||
|
interface ISvgIcon {
|
||||||
|
className?: string,
|
||||||
|
alt?: string,
|
||||||
|
src: string,
|
||||||
|
size?: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Renders an inline SVG with an empty frame loading state */
|
||||||
|
const SvgIcon: React.FC<ISvgIcon> = ({ src, alt, size = 24, className }): JSX.Element => {
|
||||||
|
const loader = (
|
||||||
|
<svg
|
||||||
|
className={className}
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
data-src={src}
|
||||||
|
data-testid='svg-icon-loader'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InlineSVG
|
||||||
|
className={className}
|
||||||
|
src={src}
|
||||||
|
title={alt}
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
loader={loader}
|
||||||
|
data-testid='svg-icon'
|
||||||
|
>
|
||||||
|
/* If the fetch fails, fall back to displaying the loader */
|
||||||
|
{loader}
|
||||||
|
</InlineSVG>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SvgIcon;
|
|
@ -1,9 +1,9 @@
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import InlineSVG from 'react-inlinesvg';
|
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import Icon from '../icon/icon';
|
import Icon from '../icon/icon';
|
||||||
|
import SvgIcon from '../icon/svg-icon';
|
||||||
import Tooltip from '../tooltip/tooltip';
|
import Tooltip from '../tooltip/tooltip';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
@ -72,7 +72,7 @@ const Input = React.forwardRef<HTMLInputElement, IInput>(
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
className='text-gray-400 hover:text-gray-500 h-full px-2 focus:ring-primary-500 focus:ring-2'
|
className='text-gray-400 hover:text-gray-500 h-full px-2 focus:ring-primary-500 focus:ring-2'
|
||||||
>
|
>
|
||||||
<InlineSVG
|
<SvgIcon
|
||||||
src={revealed ? require('@tabler/icons/icons/eye-off.svg') : require('@tabler/icons/icons/eye.svg')}
|
src={revealed ? require('@tabler/icons/icons/eye-off.svg') : require('@tabler/icons/icons/eye.svg')}
|
||||||
className='h-4 w-4'
|
className='h-4 w-4'
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -6,7 +6,6 @@ import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import InlineSVG from 'react-inlinesvg';
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
@ -16,6 +15,7 @@ import Avatar from 'soapbox/components/avatar';
|
||||||
import Badge from 'soapbox/components/badge';
|
import Badge from 'soapbox/components/badge';
|
||||||
import StillImage from 'soapbox/components/still_image';
|
import StillImage from 'soapbox/components/still_image';
|
||||||
import { HStack, IconButton, Menu, MenuButton, MenuItem, MenuList, MenuLink, MenuDivider } from 'soapbox/components/ui';
|
import { HStack, IconButton, Menu, MenuButton, MenuItem, MenuList, MenuLink, MenuDivider } from 'soapbox/components/ui';
|
||||||
|
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
|
||||||
import ActionButton from 'soapbox/features/ui/components/action_button';
|
import ActionButton from 'soapbox/features/ui/components/action_button';
|
||||||
import {
|
import {
|
||||||
isLocal,
|
isLocal,
|
||||||
|
@ -614,7 +614,7 @@ class Header extends ImmutablePureComponent {
|
||||||
return (
|
return (
|
||||||
<Comp key={idx} {...itemProps} className='group'>
|
<Comp key={idx} {...itemProps} className='group'>
|
||||||
<div className='flex items-center'>
|
<div className='flex items-center'>
|
||||||
<InlineSVG src={menuItem.icon} className='mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500' />
|
<SvgIcon src={menuItem.icon} className='mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500' />
|
||||||
|
|
||||||
<div className='truncate'>{menuItem.text}</div>
|
<div className='truncate'>{menuItem.text}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,12 +2,12 @@ import classNames from 'classnames';
|
||||||
import { Map as ImmutableMap } from 'immutable';
|
import { Map as ImmutableMap } from 'immutable';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import InlineSVG from 'react-inlinesvg';
|
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import AutosuggestAccountInput from 'soapbox/components/autosuggest_account_input';
|
import AutosuggestAccountInput from 'soapbox/components/autosuggest_account_input';
|
||||||
|
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
|
||||||
import { useAppSelector } from 'soapbox/hooks';
|
import { useAppSelector } from 'soapbox/hooks';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -140,12 +140,12 @@ const Search = (props: ISearch) => {
|
||||||
className='absolute inset-y-0 right-0 px-3 flex items-center cursor-pointer'
|
className='absolute inset-y-0 right-0 px-3 flex items-center cursor-pointer'
|
||||||
onClick={handleClear}
|
onClick={handleClear}
|
||||||
>
|
>
|
||||||
<InlineSVG
|
<SvgIcon
|
||||||
src={require('@tabler/icons/icons/search.svg')}
|
src={require('@tabler/icons/icons/search.svg')}
|
||||||
className={classNames('h-4 w-4 text-gray-400', { hidden: hasValue })}
|
className={classNames('h-4 w-4 text-gray-400', { hidden: hasValue })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<InlineSVG
|
<SvgIcon
|
||||||
src={require('@tabler/icons/icons/x.svg')}
|
src={require('@tabler/icons/icons/x.svg')}
|
||||||
className={classNames('h-4 w-4 text-gray-400', { hidden: !hasValue })}
|
className={classNames('h-4 w-4 text-gray-400', { hidden: !hasValue })}
|
||||||
aria-label={intl.formatMessage(messages.placeholder)}
|
aria-label={intl.formatMessage(messages.placeholder)}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import InlineSVG from 'react-inlinesvg';
|
|
||||||
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { Link, useHistory } from 'react-router-dom';
|
import { Link, useHistory } from 'react-router-dom';
|
||||||
|
@ -7,6 +6,7 @@ import { Link, useHistory } from 'react-router-dom';
|
||||||
import { changeSettingImmediate } from 'soapbox/actions/settings';
|
import { changeSettingImmediate } from 'soapbox/actions/settings';
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
import { Text } from 'soapbox/components/ui';
|
import { Text } from 'soapbox/components/ui';
|
||||||
|
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
|
||||||
|
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ const Developers = () => {
|
||||||
<Column label={intl.formatMessage(messages.heading)}>
|
<Column label={intl.formatMessage(messages.heading)}>
|
||||||
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2'>
|
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2'>
|
||||||
<Link to='/developers/apps/create' className='bg-gray-200 dark:bg-gray-600 p-4 rounded flex flex-col items-center justify-center space-y-2 hover:-translate-y-1 transition-transform'>
|
<Link to='/developers/apps/create' className='bg-gray-200 dark:bg-gray-600 p-4 rounded flex flex-col items-center justify-center space-y-2 hover:-translate-y-1 transition-transform'>
|
||||||
<InlineSVG src={require('@tabler/icons/icons/apps.svg')} className='dark:text-gray-100' />
|
<SvgIcon src={require('@tabler/icons/icons/apps.svg')} className='dark:text-gray-100' />
|
||||||
|
|
||||||
<Text>
|
<Text>
|
||||||
<FormattedMessage id='developers.navigation.app_create_label' defaultMessage='Create an app' />
|
<FormattedMessage id='developers.navigation.app_create_label' defaultMessage='Create an app' />
|
||||||
|
@ -40,7 +40,7 @@ const Developers = () => {
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link to='/developers/settings_store' className='bg-gray-200 dark:bg-gray-600 p-4 rounded flex flex-col items-center justify-center space-y-2 hover:-translate-y-1 transition-transform'>
|
<Link to='/developers/settings_store' className='bg-gray-200 dark:bg-gray-600 p-4 rounded flex flex-col items-center justify-center space-y-2 hover:-translate-y-1 transition-transform'>
|
||||||
<InlineSVG src={require('@tabler/icons/icons/code-plus.svg')} className='dark:text-gray-100' />
|
<SvgIcon src={require('@tabler/icons/icons/code-plus.svg')} className='dark:text-gray-100' />
|
||||||
|
|
||||||
<Text>
|
<Text>
|
||||||
<FormattedMessage id='developers.navigation.settings_store_label' defaultMessage='Settings store' />
|
<FormattedMessage id='developers.navigation.settings_store_label' defaultMessage='Settings store' />
|
||||||
|
@ -48,7 +48,7 @@ const Developers = () => {
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link to='/error' className='bg-gray-200 dark:bg-gray-600 p-4 rounded flex flex-col items-center justify-center space-y-2 hover:-translate-y-1 transition-transform'>
|
<Link to='/error' className='bg-gray-200 dark:bg-gray-600 p-4 rounded flex flex-col items-center justify-center space-y-2 hover:-translate-y-1 transition-transform'>
|
||||||
<InlineSVG src={require('@tabler/icons/icons/mood-sad.svg')} className='dark:text-gray-100' />
|
<SvgIcon src={require('@tabler/icons/icons/mood-sad.svg')} className='dark:text-gray-100' />
|
||||||
|
|
||||||
<Text>
|
<Text>
|
||||||
<FormattedMessage id='developers.navigation.intentional_error_label' defaultMessage='Trigger an error' />
|
<FormattedMessage id='developers.navigation.intentional_error_label' defaultMessage='Trigger an error' />
|
||||||
|
@ -56,7 +56,7 @@ const Developers = () => {
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<button onClick={leaveDevelopers} className='bg-gray-200 dark:bg-gray-600 p-4 rounded flex flex-col items-center justify-center space-y-2 hover:-translate-y-1 transition-transform'>
|
<button onClick={leaveDevelopers} className='bg-gray-200 dark:bg-gray-600 p-4 rounded flex flex-col items-center justify-center space-y-2 hover:-translate-y-1 transition-transform'>
|
||||||
<InlineSVG src={require('@tabler/icons/icons/logout.svg')} className='dark:text-gray-100' />
|
<SvgIcon src={require('@tabler/icons/icons/logout.svg')} className='dark:text-gray-100' />
|
||||||
|
|
||||||
<Text>
|
<Text>
|
||||||
<FormattedMessage id='developers.navigation.leave_developers_label' defaultMessage='Leave developers' />
|
<FormattedMessage id='developers.navigation.leave_developers_label' defaultMessage='Leave developers' />
|
||||||
|
|
|
@ -355,9 +355,10 @@ class ActionBar extends React.PureComponent<IActionBar, IActionBarState> {
|
||||||
'😮': messages.reactionOpenMouth,
|
'😮': messages.reactionOpenMouth,
|
||||||
'😢': messages.reactionCry,
|
'😢': messages.reactionCry,
|
||||||
'😩': messages.reactionWeary,
|
'😩': messages.reactionWeary,
|
||||||
|
'': messages.favourite,
|
||||||
};
|
};
|
||||||
|
|
||||||
const meEmojiTitle = intl.formatMessage(meEmojiReact ? reactMessages[meEmojiReact] : messages.favourite);
|
const meEmojiTitle = intl.formatMessage(reactMessages[meEmojiReact || ''] || messages.favourite);
|
||||||
|
|
||||||
const menu: Menu = [];
|
const menu: Menu = [];
|
||||||
|
|
||||||
|
|
|
@ -185,4 +185,10 @@ describe('normalizeInstance()', () => {
|
||||||
|
|
||||||
expect(result.version).toEqual('3.5.0-rc1');
|
expect(result.version).toEqual('3.5.0-rc1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('normalizes Pixelfed instance', () => {
|
||||||
|
const instance = require('soapbox/__fixtures__/pixelfed-instance.json');
|
||||||
|
const result = normalizeInstance(instance);
|
||||||
|
expect(result.title).toBe('pixelfed');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import {
|
||||||
|
removeVS16s,
|
||||||
|
toCodePoints,
|
||||||
|
} from '../emoji';
|
||||||
|
|
||||||
|
const ASCII_HEART = '❤'; // '\u2764\uFE0F'
|
||||||
|
const RED_HEART_RGI = '❤️'; // '\u2764'
|
||||||
|
const JOY = '😂';
|
||||||
|
|
||||||
|
describe('removeVS16s()', () => {
|
||||||
|
it('removes Variation Selector-16 characters from emoji', () => {
|
||||||
|
// Sanity check
|
||||||
|
expect(ASCII_HEART).not.toBe(RED_HEART_RGI);
|
||||||
|
|
||||||
|
// It normalizes an emoji with VS16s
|
||||||
|
expect(removeVS16s(RED_HEART_RGI)).toBe(ASCII_HEART);
|
||||||
|
|
||||||
|
// Leaves a regular emoji alone
|
||||||
|
expect(removeVS16s(JOY)).toBe(JOY);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toCodePoints()', () => {
|
||||||
|
it('converts a plain emoji', () => {
|
||||||
|
expect(toCodePoints('😂')).toEqual(['1f602']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts a VS16 emoji', () => {
|
||||||
|
expect(toCodePoints(RED_HEART_RGI)).toEqual(['2764', 'fe0f']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts an ASCII character', () => {
|
||||||
|
expect(toCodePoints(ASCII_HEART)).toEqual(['2764']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts a sequence emoji', () => {
|
||||||
|
expect(toCodePoints('🇺🇸')).toEqual(['1f1fa', '1f1f8']);
|
||||||
|
});
|
||||||
|
});
|
|
@ -23,6 +23,15 @@ describe('parseVersion', () => {
|
||||||
compatVersion: '3.0.0',
|
compatVersion: '3.0.0',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('with a Pixelfed version string', () => {
|
||||||
|
const version = '2.7.2 (compatible; Pixelfed 0.11.2)';
|
||||||
|
expect(parseVersion(version)).toEqual({
|
||||||
|
software: 'Pixelfed',
|
||||||
|
version: '0.11.2',
|
||||||
|
compatVersion: '2.7.2',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getFeatures', () => {
|
describe('getFeatures', () => {
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
// Taken from twemoji-parser
|
||||||
|
// https://github.com/twitter/twemoji-parser/blob/a97ef3994e4b88316812926844d51c296e889f76/src/index.js
|
||||||
|
|
||||||
|
/** Remove Variation Selector-16 characters from emoji */
|
||||||
|
// https://emojipedia.org/variation-selector-16/
|
||||||
|
const removeVS16s = (rawEmoji: string): string => {
|
||||||
|
const vs16RegExp = /\uFE0F/g;
|
||||||
|
const zeroWidthJoiner = String.fromCharCode(0x200d);
|
||||||
|
return rawEmoji.indexOf(zeroWidthJoiner) < 0 ? rawEmoji.replace(vs16RegExp, '') : rawEmoji;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Convert emoji into an array of Unicode codepoints */
|
||||||
|
const toCodePoints = (unicodeSurrogates: string): string[] => {
|
||||||
|
const points = [];
|
||||||
|
let char = 0;
|
||||||
|
let previous = 0;
|
||||||
|
let i = 0;
|
||||||
|
while (i < unicodeSurrogates.length) {
|
||||||
|
char = unicodeSurrogates.charCodeAt(i++);
|
||||||
|
if (previous) {
|
||||||
|
points.push((0x10000 + ((previous - 0xd800) << 10) + (char - 0xdc00)).toString(16));
|
||||||
|
previous = 0;
|
||||||
|
} else if (char > 0xd800 && char <= 0xdbff) {
|
||||||
|
previous = char;
|
||||||
|
} else {
|
||||||
|
points.push(char.toString(16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
removeVS16s,
|
||||||
|
toCodePoints,
|
||||||
|
};
|
|
@ -19,6 +19,7 @@ export const MASTODON = 'Mastodon';
|
||||||
export const PLEROMA = 'Pleroma';
|
export const PLEROMA = 'Pleroma';
|
||||||
export const MITRA = 'Mitra';
|
export const MITRA = 'Mitra';
|
||||||
export const TRUTHSOCIAL = 'TruthSocial';
|
export const TRUTHSOCIAL = 'TruthSocial';
|
||||||
|
export const PIXELFED = 'Pixelfed';
|
||||||
|
|
||||||
const getInstanceFeatures = (instance: Instance) => {
|
const getInstanceFeatures = (instance: Instance) => {
|
||||||
const v = parseVersion(instance.version);
|
const v = parseVersion(instance.version);
|
||||||
|
@ -41,6 +42,7 @@ const getInstanceFeatures = (instance: Instance) => {
|
||||||
bookmarks: any([
|
bookmarks: any([
|
||||||
v.software === MASTODON && gte(v.compatVersion, '3.1.0'),
|
v.software === MASTODON && gte(v.compatVersion, '3.1.0'),
|
||||||
v.software === PLEROMA && gte(v.version, '0.9.9'),
|
v.software === PLEROMA && gte(v.version, '0.9.9'),
|
||||||
|
v.software === PIXELFED,
|
||||||
]),
|
]),
|
||||||
lists: any([
|
lists: any([
|
||||||
v.software === MASTODON && gte(v.compatVersion, '2.1.0'),
|
v.software === MASTODON && gte(v.compatVersion, '2.1.0'),
|
||||||
|
@ -73,6 +75,7 @@ const getInstanceFeatures = (instance: Instance) => {
|
||||||
conversations: any([
|
conversations: any([
|
||||||
v.software === MASTODON && gte(v.compatVersion, '2.6.0'),
|
v.software === MASTODON && gte(v.compatVersion, '2.6.0'),
|
||||||
v.software === PLEROMA && gte(v.version, '0.9.9'),
|
v.software === PLEROMA && gte(v.version, '0.9.9'),
|
||||||
|
v.software === PIXELFED,
|
||||||
]),
|
]),
|
||||||
emojiReacts: v.software === PLEROMA && gte(v.version, '2.0.0'),
|
emojiReacts: v.software === PLEROMA && gte(v.version, '2.0.0'),
|
||||||
emojiReactsRGI: v.software === PLEROMA && gte(v.version, '2.2.49'),
|
emojiReactsRGI: v.software === PLEROMA && gte(v.version, '2.2.49'),
|
||||||
|
@ -83,7 +86,7 @@ const getInstanceFeatures = (instance: Instance) => {
|
||||||
chats: v.software === PLEROMA && gte(v.version, '2.1.0'),
|
chats: v.software === PLEROMA && gte(v.version, '2.1.0'),
|
||||||
chatsV2: v.software === PLEROMA && gte(v.version, '2.3.0'),
|
chatsV2: v.software === PLEROMA && gte(v.version, '2.3.0'),
|
||||||
scopes: v.software === PLEROMA ? 'read write follow push admin' : 'read write follow push',
|
scopes: v.software === PLEROMA ? 'read write follow push admin' : 'read write follow push',
|
||||||
federating: federation.get('enabled', true), // Assume true unless explicitly false
|
federating: federation.get('enabled', true) === true, // Assume true unless explicitly false
|
||||||
richText: v.software === PLEROMA,
|
richText: v.software === PLEROMA,
|
||||||
securityAPI: any([
|
securityAPI: any([
|
||||||
v.software === PLEROMA,
|
v.software === PLEROMA,
|
||||||
|
|
Ładowanie…
Reference in New Issue