Merge remote-tracking branch 'glitch/main'

pull/62/head
kouhai dev 2023-05-22 00:10:53 -07:00
commit 2408f1046f
354 changed files with 18892 additions and 13738 deletions

View File

@ -4,10 +4,12 @@ module.exports = {
extends: [ extends: [
'eslint:recommended', 'eslint:recommended',
'plugin:react/recommended', 'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended', 'plugin:jsx-a11y/recommended',
'plugin:import/recommended', 'plugin:import/recommended',
'plugin:promise/recommended', 'plugin:promise/recommended',
'plugin:jsdoc/recommended', 'plugin:jsdoc/recommended',
'plugin:prettier/recommended',
], ],
env: { env: {
@ -61,20 +63,9 @@ module.exports = {
}, },
rules: { rules: {
'brace-style': 'warn',
'comma-dangle': ['error', 'always-multiline'],
'comma-spacing': [
'warn',
{
before: false,
after: true,
},
],
'comma-style': ['warn', 'last'],
'consistent-return': 'error', 'consistent-return': 'error',
'dot-notation': 'error', 'dot-notation': 'error',
eqeqeq: ['error', 'always', { 'null': 'ignore' }], eqeqeq: ['error', 'always', { 'null': 'ignore' }],
indent: ['warn', 2],
'jsx-quotes': ['error', 'prefer-single'], 'jsx-quotes': ['error', 'prefer-single'],
'no-case-declarations': 'off', 'no-case-declarations': 'off',
'no-catch-shadow': 'error', 'no-catch-shadow': 'error',
@ -94,7 +85,6 @@ module.exports = {
{ property: 'substr', message: 'Use .slice instead of .substr.' }, { property: 'substr', message: 'Use .slice instead of .substr.' },
], ],
'no-self-assign': 'off', 'no-self-assign': 'off',
'no-trailing-spaces': 'warn',
'no-unused-expressions': 'error', 'no-unused-expressions': 'error',
'no-unused-vars': 'off', 'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': [ '@typescript-eslint/no-unused-vars': [
@ -102,32 +92,18 @@ module.exports = {
{ {
vars: 'all', vars: 'all',
args: 'after-used', args: 'after-used',
destructuredArrayIgnorePattern: '^_',
ignoreRestSiblings: true, ignoreRestSiblings: true,
}, },
], ],
'object-curly-spacing': ['error', 'always'],
'padded-blocks': [
'error',
{
classes: 'always',
},
],
quotes: ['error', 'single'],
semi: 'error',
'valid-typeof': 'error', 'valid-typeof': 'error',
'react/jsx-filename-extension': ['error', { extensions: ['.jsx', 'tsx'] }], 'react/jsx-filename-extension': ['error', { extensions: ['.jsx', 'tsx'] }],
'react/jsx-boolean-value': 'error', 'react/jsx-boolean-value': 'error',
'react/jsx-closing-bracket-location': ['error', 'line-aligned'],
'react/jsx-curly-spacing': 'error',
'react/display-name': 'off', 'react/display-name': 'off',
'react/jsx-equals-spacing': 'error', 'react/jsx-equals-spacing': 'error',
'react/jsx-first-prop-new-line': ['error', 'multiline-multiprop'],
'react/jsx-indent': ['error', 2],
'react/jsx-no-bind': 'error', 'react/jsx-no-bind': 'error',
'react/jsx-no-target-blank': 'off', 'react/jsx-no-target-blank': 'off',
'react/jsx-tag-spacing': 'error',
'react/jsx-wrap-multilines': 'error',
'react/no-deprecated': 'off', 'react/no-deprecated': 'off',
'react/no-unknown-property': 'off', 'react/no-unknown-property': 'off',
'react/self-closing-comp': 'error', 'react/self-closing-comp': 'error',
@ -208,6 +184,9 @@ module.exports = {
], ],
}, },
], ],
'import/no-amd': 'error',
'import/no-commonjs': 'error',
'import/no-import-module-exports': 'error',
'import/no-webpack-loader-syntax': 'error', 'import/no-webpack-loader-syntax': 'error',
'promise/always-return': 'off', 'promise/always-return': 'off',
@ -255,6 +234,7 @@ module.exports = {
'*.config.js', '*.config.js',
'.*rc.js', '.*rc.js',
'ide-helper.js', 'ide-helper.js',
'config/webpack/**/*',
], ],
env: { env: {
@ -264,6 +244,10 @@ module.exports = {
parserOptions: { parserOptions: {
sourceType: 'script', sourceType: 'script',
}, },
rules: {
'import/no-commonjs': 'off',
},
}, },
{ {
files: [ files: [
@ -275,17 +259,25 @@ module.exports = {
'eslint:recommended', 'eslint:recommended',
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'plugin:react/recommended', 'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended', 'plugin:jsx-a11y/recommended',
'plugin:import/recommended', 'plugin:import/recommended',
'plugin:import/typescript', 'plugin:import/typescript',
'plugin:promise/recommended', 'plugin:promise/recommended',
'plugin:jsdoc/recommended', 'plugin:jsdoc/recommended',
'plugin:prettier/recommended',
], ],
rules: { rules: {
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-explicit-any': 'off',
'jsdoc/require-jsdoc': 'off', 'jsdoc/require-jsdoc': 'off',
// Those rules set stricter rules for TS files
// to enforce better practices when converting from JS
'import/no-default-export': 'warn',
'react/prefer-stateless-function': 'warn',
'react/function-component-definition': ['error', { namedComponents: 'arrow-function' }],
}, },
}, },
{ {
@ -298,5 +290,13 @@ module.exports = {
jest: true, jest: true,
}, },
}, },
{
files: [
'streaming/**/*',
],
rules: {
'import/no-commonjs': 'off',
},
},
], ],
}; };

View File

@ -70,8 +70,6 @@ app/javascript/styles/mastodon/reset.scss
# Ignore Javascript pending https://github.com/mastodon/mastodon/pull/23631 # Ignore Javascript pending https://github.com/mastodon/mastodon/pull/23631
*.js *.js
*.jsx *.jsx
*.ts
*.tsx
# Ignore HTML till cleaned and included in CI # Ignore HTML till cleaned and included in CI
*.html *.html

View File

@ -1,3 +1,4 @@
module.exports = { module.exports = {
singleQuote: true singleQuote: true,
jsxSingleQuote: true
} }

View File

@ -157,7 +157,7 @@ Metrics/MethodLength:
- 'lib/mastodon/*_cli.rb' - 'lib/mastodon/*_cli.rb'
# Reason: # Reason:
# https://docs.rubocop.org/rubocop/cops_style.html#stylerescuestandarderror # https://docs.rubocop.org/rubocop/cops_metrics.html#metricsmodulelength
Metrics/ModuleLength: Metrics/ModuleLength:
CountAsOne: [array, heredoc] CountAsOne: [array, heredoc]

View File

@ -1,3 +1,3 @@
require('../styles/mailer.scss'); import '../styles/mailer.scss';
require.context('../icons'); require.context('../icons');

View File

@ -2,7 +2,7 @@
import 'packs/public-path'; import 'packs/public-path';
const { delegate } = require('@rails/ujs'); import { delegate } from '@rails/ujs';
const getProfileAvatarAnimationHandler = (swapTo) => { const getProfileAvatarAnimationHandler = (swapTo) => {
//animate avatar gifs on the profile page when moused over //animate avatar gifs on the profile page when moused over

View File

@ -3,7 +3,7 @@
import 'packs/public-path'; import 'packs/public-path';
import escapeTextContentForBrowser from 'escape-html'; import escapeTextContentForBrowser from 'escape-html';
const { delegate } = require('@rails/ujs'); import { delegate } from '@rails/ujs';
import emojify from '../mastodon/features/emoji/emoji'; import emojify from '../mastodon/features/emoji/emoji';

View File

@ -1,7 +1,8 @@
import { createAction } from '@reduxjs/toolkit'; import { createAction } from '@reduxjs/toolkit';
import type { LayoutType } from '../is_mobile';
type ChangeLayoutPayload = { type ChangeLayoutPayload = {
layout: 'mobile' | 'single-column' | 'multi-column'; layout: LayoutType;
}; };
export const changeLayout = export const changeLayout =
createAction<ChangeLayoutPayload>('APP_LAYOUT_CHANGE'); createAction<ChangeLayoutPayload>('APP_LAYOUT_CHANGE');

View File

@ -1,6 +1,6 @@
import api from '../api'; import api from '../api';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import compareId from '../compare_id'; import { compareId } from '../compare_id';
import { List as ImmutableList } from 'immutable'; import { List as ImmutableList } from 'immutable';
export const MARKERS_FETCH_REQUEST = 'MARKERS_FETCH_REQUEST'; export const MARKERS_FETCH_REQUEST = 'MARKERS_FETCH_REQUEST';

View File

@ -13,7 +13,7 @@ import { defineMessages } from 'react-intl';
import { List as ImmutableList } from 'immutable'; import { List as ImmutableList } from 'immutable';
import { unescapeHTML } from 'flavours/glitch/utils/html'; import { unescapeHTML } from 'flavours/glitch/utils/html';
import { usePendingItems as preferPendingItems } from 'flavours/glitch/initial_state'; import { usePendingItems as preferPendingItems } from 'flavours/glitch/initial_state';
import compareId from 'flavours/glitch/compare_id'; import { compareId } from 'flavours/glitch/compare_id';
import { requestNotificationPermission } from 'flavours/glitch/utils/notifications'; import { requestNotificationPermission } from 'flavours/glitch/utils/notifications';
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';

View File

@ -52,8 +52,10 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
/** /**
* @param {function(Function, Function): void} fallback * @param {function(Function, Function): void} fallback
*/ */
const useFallback = fallback => { const useFallback = fallback => {
fallback(dispatch, () => { fallback(dispatch, () => {
// eslint-disable-next-line react-hooks/rules-of-hooks -- this is not a react hook
pollingId = setTimeout(() => useFallback(fallback), 20000 + randomUpTo(20000)); pollingId = setTimeout(() => useFallback(fallback), 20000 + randomUpTo(20000));
}); });
}; };

View File

@ -2,7 +2,7 @@ import { importFetchedStatus, importFetchedStatuses } from './importer';
import { submitMarkers } from './markers'; import { submitMarkers } from './markers';
import api, { getLinks } from 'flavours/glitch/api'; import api, { getLinks } from 'flavours/glitch/api';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import compareId from 'flavours/glitch/compare_id'; import { compareId } from 'flavours/glitch/compare_id';
import { usePendingItems as preferPendingItems } from 'flavours/glitch/initial_state'; import { usePendingItems as preferPendingItems } from 'flavours/glitch/initial_state';
import { toServerSideType } from 'flavours/glitch/utils/filters'; import { toServerSideType } from 'flavours/glitch/utils/filters';

View File

@ -98,9 +98,9 @@ export const decode83 = (str: string) => {
}; };
export const intToRGB = (int: number) => ({ export const intToRGB = (int: number) => ({
r: Math.max(0, (int >> 16)), r: Math.max(0, int >> 16),
g: Math.max(0, (int >> 8) & 255), g: Math.max(0, (int >> 8) & 255),
b: Math.max(0, (int & 255)), b: Math.max(0, int & 255),
}); });
export const getAverageFromBlurhash = (blurhash: string) => { export const getAverageFromBlurhash = (blurhash: string) => {

View File

@ -1,4 +1,4 @@
export default function compareId (id1: string, id2: string) { export function compareId(id1: string, id2: string) {
if (id1 === id2) { if (id1 === id2) {
return 0; return 0;
} }

View File

@ -1,14 +1,14 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Avatar from './avatar'; import { Avatar } from './avatar';
import DisplayName from './display_name'; import DisplayName from './display_name';
import Permalink from './permalink'; import Permalink from './permalink';
import IconButton from './icon_button'; import { IconButton } from './icon_button';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { me } from 'flavours/glitch/initial_state'; import { me } from 'flavours/glitch/initial_state';
import RelativeTimestamp from './relative_timestamp'; import { RelativeTimestamp } from './relative_timestamp';
import Skeleton from 'flavours/glitch/components/skeleton'; import Skeleton from 'flavours/glitch/components/skeleton';
const messages = defineMessages({ const messages = defineMessages({

View File

@ -16,13 +16,10 @@ const obfuscatedCount = (count: number) => {
type Props = { type Props = {
value: number; value: number;
obfuscate?: boolean; obfuscate?: boolean;
} };
export const AnimatedNumber: React.FC<Props> = ({ export const AnimatedNumber: React.FC<Props> = ({ value, obfuscate }) => {
value,
obfuscate,
})=> {
const [previousValue, setPreviousValue] = useState(value); const [previousValue, setPreviousValue] = useState(value);
const [direction, setDirection] = useState<1|-1>(1); const [direction, setDirection] = useState<1 | -1>(1);
if (previousValue !== value) { if (previousValue !== value) {
setPreviousValue(value); setPreviousValue(value);
@ -30,29 +27,48 @@ export const AnimatedNumber: React.FC<Props> = ({
} }
const willEnter = useCallback(() => ({ y: -1 * direction }), [direction]); const willEnter = useCallback(() => ({ y: -1 * direction }), [direction]);
const willLeave = useCallback(() => ({ y: spring(1 * direction, { damping: 35, stiffness: 400 }) }), [direction]); const willLeave = useCallback(
() => ({ y: spring(1 * direction, { damping: 35, stiffness: 400 }) }),
[direction]
);
if (reduceMotion) { if (reduceMotion) {
return obfuscate ? <>{obfuscatedCount(value)}</> : <ShortNumber value={value} />; return obfuscate ? (
<>{obfuscatedCount(value)}</>
) : (
<ShortNumber value={value} />
);
} }
const styles = [{ const styles = [
{
key: `${value}`, key: `${value}`,
data: value, data: value,
style: { y: spring(0, { damping: 35, stiffness: 400 }) }, style: { y: spring(0, { damping: 35, stiffness: 400 }) },
}]; },
];
return ( return (
<TransitionMotion styles={styles} willEnter={willEnter} willLeave={willLeave}> <TransitionMotion
{items => ( styles={styles}
willEnter={willEnter}
willLeave={willLeave}
>
{(items) => (
<span className='animated-number'> <span className='animated-number'>
{items.map(({ key, data, style }) => ( {items.map(({ key, data, style }) => (
<span key={key} style={{ position: (direction * style.y) > 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : <ShortNumber value={data} />}</span> <span
key={key}
style={{
position: direction * style.y > 0 ? 'absolute' : 'static',
transform: `translateY(${style.y * 100}%)`,
}}
>
{obfuscate ? obfuscatedCount(data) : <ShortNumber value={data} />}
</span>
))} ))}
</span> </span>
)} )}
</TransitionMotion> </TransitionMotion>
); );
}; };
export default AnimatedNumber;

View File

@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
const filename = url => url.split('/').pop().split('#')[0].split('?')[0]; const filename = url => url.split('/').pop().split('#')[0].split('?')[0];

View File

@ -1,7 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { autoPlayGif } from 'flavours/glitch/initial_state'; import { autoPlayGif } from 'flavours/glitch/initial_state';
import { useHovering } from 'hooks/useHovering'; import { useHovering } from 'flavours/glitch/hooks/useHovering';
import type { Account } from 'flavours/glitch/types/resources'; import type { Account } from 'flavours/glitch/types/resources';
type Props = { type Props = {
@ -10,7 +10,7 @@ type Props = {
size: number; size: number;
style?: React.CSSProperties; style?: React.CSSProperties;
inline?: boolean; inline?: boolean;
} };
export const Avatar: React.FC<Props> = ({ export const Avatar: React.FC<Props> = ({
account, account,
@ -19,7 +19,8 @@ export const Avatar: React.FC<Props> = ({
inline = false, inline = false,
style: styleFromParent, style: styleFromParent,
}) => { }) => {
const { hovering, handleMouseEnter, handleMouseLeave } = useHovering(autoPlayGif); const { hovering, handleMouseEnter, handleMouseLeave } =
useHovering(autoPlayGif);
const style = { const style = {
...styleFromParent, ...styleFromParent,
@ -29,12 +30,18 @@ export const Avatar: React.FC<Props> = ({
}; };
if (account) { if (account) {
style.backgroundImage = `url(${account.get(hovering ? 'avatar' : 'avatar_static')})`; style.backgroundImage = `url(${account.get(
hovering ? 'avatar' : 'avatar_static'
)})`;
} }
return ( return (
<div <div
className={classNames('account__avatar', { 'account__avatar-inline': inline }, className)} className={classNames(
'account__avatar',
{ 'account__avatar-inline': inline },
className
)}
onMouseEnter={handleMouseEnter} onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave} onMouseLeave={handleMouseLeave}
style={style} style={style}
@ -44,5 +51,3 @@ export const Avatar: React.FC<Props> = ({
/> />
); );
}; };
export default Avatar;

View File

@ -9,7 +9,6 @@ export default class AvatarComposite extends React.PureComponent {
accounts: ImmutablePropTypes.list.isRequired, accounts: ImmutablePropTypes.list.isRequired,
animate: PropTypes.bool, animate: PropTypes.bool,
size: PropTypes.number.isRequired, size: PropTypes.number.isRequired,
onAccountClick: PropTypes.func.isRequired,
}; };
static defaultProps = { static defaultProps = {
@ -80,15 +79,7 @@ export default class AvatarComposite extends React.PureComponent {
}; };
return ( return (
<a <div key={account.get('id')} style={style} data-avatar-of={`@${account.get('acct')}`} />
href={account.get('url')}
target='_blank'
onClick={(e) => this.props.onAccountClick(account.get('acct'), e)}
title={`@${account.get('acct')}`}
key={account.get('id')}
>
<div style={style} data-avatar-of={`@${account.get('acct')}`} />
</a>
); );
} }

View File

@ -8,14 +8,14 @@ type Props = {
dummy?: boolean; // Whether dummy mode is enabled. If enabled, nothing is rendered and canvas left untouched dummy?: boolean; // Whether dummy mode is enabled. If enabled, nothing is rendered and canvas left untouched
children?: never; children?: never;
[key: string]: any; [key: string]: any;
} };
function Blurhash({ const Blurhash: React.FC<Props> = ({
hash, hash,
width = 32, width = 32,
height = width, height = width,
dummy = false, dummy = false,
...canvasProps ...canvasProps
}: Props) { }) => {
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => { useEffect(() => {
@ -40,6 +40,8 @@ function Blurhash({
return ( return (
<canvas {...canvasProps} ref={canvasRef} width={width} height={height} /> <canvas {...canvasProps} ref={canvasRef} width={width} height={height} />
); );
} };
export default React.memo(Blurhash); const MemoizedBlurhash = React.memo(Blurhash);
export { MemoizedBlurhash as Blurhash };

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
export default class ColumnBackButton extends React.PureComponent { export default class ColumnBackButton extends React.PureComponent {

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
export default class ColumnBackButtonSlim extends React.PureComponent { export default class ColumnBackButtonSlim extends React.PureComponent {

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import classNames from 'classnames'; import classNames from 'classnames';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
const messages = defineMessages({ const messages = defineMessages({
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' }, show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import IconButton from './icon_button'; import { IconButton } from './icon_button';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { injectIntl, defineMessages } from 'react-intl'; import { injectIntl, defineMessages } from 'react-intl';
import { bannerSettings } from 'flavours/glitch/settings'; import { bannerSettings } from 'flavours/glitch/settings';

View File

@ -9,12 +9,9 @@ export default class DisplayName extends React.PureComponent {
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map, account: ImmutablePropTypes.map,
className: PropTypes.string,
inline: PropTypes.bool,
localDomain: PropTypes.string,
others: ImmutablePropTypes.list, others: ImmutablePropTypes.list,
handleClick: PropTypes.func, localDomain: PropTypes.string,
onAccountClick: PropTypes.func, inline: PropTypes.bool,
}; };
handleMouseEnter = ({ currentTarget }) => { handleMouseEnter = ({ currentTarget }) => {
@ -43,48 +40,30 @@ export default class DisplayName extends React.PureComponent {
} }
}; };
render() { render () {
const { account, className, inline, localDomain, others, onAccountClick } = this.props; const { others, localDomain, inline } = this.props;
const computedClass = classNames('display-name', { inline }, className); let displayName, suffix, account;
let displayName, suffix; if (others && others.size > 1) {
let acct; displayName = others.take(2).map(a => <bdi key={a.get('id')}><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi>).reduce((prev, cur) => [prev, ', ', cur]);
if (account) { if (others.size - 2 > 0) {
acct = account.get('acct'); suffix = `+${others.size - 2}`;
}
} else if ((others && others.size > 0) || this.props.account) {
if (others && others.size > 0) {
account = others.first();
} else {
account = this.props.account;
}
let acct = account.get('acct');
if (acct.indexOf('@') === -1 && localDomain) { if (acct.indexOf('@') === -1 && localDomain) {
acct = `${acct}@${localDomain}`; acct = `${acct}@${localDomain}`;
} }
}
if (others && others.size > 0) {
displayName = others.take(2).map(a => (
<a
key={a.get('id')}
href={a.get('url')}
target='_blank'
onClick={(e) => onAccountClick(a.get('acct'), e)}
title={`@${a.get('acct')}`}
rel='noopener noreferrer'
>
<bdi key={a.get('id')}>
<strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} />
</bdi>
</a>
)).reduce((prev, cur) => [prev, ', ', cur]);
if (others.size - 2 > 0) {
displayName.push(` +${others.size - 2}`);
}
suffix = (
<a href={account.get('url')} target='_blank' onClick={(e) => onAccountClick(account.get('acct'), e)} rel='noopener noreferrer'>
<span className='display-name__account'>@{acct}</span>
</a>
);
} else if (account) {
displayName = <bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>; displayName = <bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>;
suffix = <span className='display-name__account'>@{acct}</span>; suffix = <span className='display-name__account'>@{acct}</span>;
} else { } else {
@ -93,7 +72,7 @@ export default class DisplayName extends React.PureComponent {
} }
return ( return (
<span className={computedClass} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> <span className={classNames('display-name', { inline })} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
{displayName} {displayName}
{inline ? ' ' : null} {inline ? ' ' : null}
{suffix} {suffix}

View File

@ -1,43 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import IconButton from './icon_button';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
const messages = defineMessages({
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
});
class Account extends ImmutablePureComponent {
static propTypes = {
domain: PropTypes.string,
onUnblockDomain: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
handleDomainUnblock = () => {
this.props.onUnblockDomain(this.props.domain);
};
render () {
const { domain, intl } = this.props;
return (
<div className='domain'>
<div className='domain__wrapper'>
<span className='domain__domain-name'>
<strong>{domain}</strong>
</span>
<div className='domain__buttons'>
<IconButton active icon='unlock' title={intl.formatMessage(messages.unblockDomain, { domain })} onClick={this.handleDomainUnblock} />
</div>
</div>
</div>
);
}
}
export default injectIntl(Account);

View File

@ -0,0 +1,42 @@
import React, { useCallback } from 'react';
import { IconButton } from './icon_button';
import { InjectedIntl, defineMessages, injectIntl } from 'react-intl';
const messages = defineMessages({
unblockDomain: {
id: 'account.unblock_domain',
defaultMessage: 'Unblock domain {domain}',
},
});
type Props = {
domain: string;
onUnblockDomain: (domain: string) => void;
intl: InjectedIntl;
};
const _Domain: React.FC<Props> = ({ domain, onUnblockDomain, intl }) => {
const handleDomainUnblock = useCallback(() => {
onUnblockDomain(domain);
}, [domain, onUnblockDomain]);
return (
<div className='domain'>
<div className='domain__wrapper'>
<span className='domain__domain-name'>
<strong>{domain}</strong>
</span>
<div className='domain__buttons'>
<IconButton
active
icon='unlock'
title={intl.formatMessage(messages.unblockDomain, { domain })}
onClick={handleDomainUnblock}
/>
</div>
</div>
</div>
);
};
export const Domain = injectIntl(_Domain);

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import IconButton from './icon_button'; import { IconButton } from './icon_button';
import Overlay from 'react-overlays/Overlay'; import Overlay from 'react-overlays/Overlay';
import { supportsPassiveEvents } from 'detect-passive-events'; import { supportsPassiveEvents } from 'detect-passive-events';
import classNames from 'classnames'; import classNames from 'classnames';

View File

@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FormattedMessage, injectIntl } from 'react-intl'; import { FormattedMessage, injectIntl } from 'react-intl';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import DropdownMenu from './containers/dropdown_menu_container'; import DropdownMenu from './containers/dropdown_menu_container';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { openModal } from 'flavours/glitch/actions/modal'; import { openModal } from 'flavours/glitch/actions/modal';
import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp';
import InlineAccount from 'flavours/glitch/components/inline_account'; import InlineAccount from 'flavours/glitch/components/inline_account';
const mapDispatchToProps = (dispatch, { statusId }) => ({ const mapDispatchToProps = (dispatch, { statusId }) => ({

View File

@ -8,7 +8,7 @@ type Props = {
width: number; width: number;
height: number; height: number;
onClick?: () => void; onClick?: () => void;
} };
export const GIFV: React.FC<Props> = ({ export const GIFV: React.FC<Props> = ({
src, src,
@ -17,19 +17,23 @@ export const GIFV: React.FC<Props> = ({
width, width,
height, height,
onClick, onClick,
})=> { }) => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const handleLoadedData: React.ReactEventHandler<HTMLVideoElement> = useCallback(() => { const handleLoadedData: React.ReactEventHandler<HTMLVideoElement> =
useCallback(() => {
setLoading(false); setLoading(false);
}, [setLoading]); }, [setLoading]);
const handleClick: React.MouseEventHandler = useCallback((e) => { const handleClick: React.MouseEventHandler = useCallback(
(e) => {
if (onClick) { if (onClick) {
e.stopPropagation(); e.stopPropagation();
onClick(); onClick();
} }
}, [onClick]); },
[onClick]
);
return ( return (
<div className='gifv' style={{ position: 'relative' }}> <div className='gifv' style={{ position: 'relative' }}>
@ -64,5 +68,3 @@ export const GIFV: React.FC<Props> = ({
</div> </div>
); );
}; };
export default GIFV;

View File

@ -7,8 +7,15 @@ type Props = {
fixedWidth?: boolean; fixedWidth?: boolean;
children?: never; children?: never;
[key: string]: any; [key: string]: any;
} };
export const Icon: React.FC<Props> = ({ id, className, fixedWidth, ...other }) => export const Icon: React.FC<Props> = ({
<i className={classNames('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })} {...other} />; id,
className,
export default Icon; fixedWidth,
...other
}) => (
<i
className={classNames('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })}
{...other}
/>
);

View File

@ -21,18 +21,17 @@ type Props = {
animate: boolean; animate: boolean;
overlay: boolean; overlay: boolean;
tabIndex: number; tabIndex: number;
label: string; label?: string;
counter?: number; counter?: number;
obfuscateCount?: boolean; obfuscateCount?: boolean;
href?: string; href?: string;
ariaHidden: boolean; ariaHidden: boolean;
} };
type States = { type States = {
activate: boolean, activate: boolean;
deactivate: boolean, deactivate: boolean;
} };
export default class IconButton extends React.PureComponent<Props, States> { export class IconButton extends React.PureComponent<Props, States> {
static defaultProps = { static defaultProps = {
size: 18, size: 18,
active: false, active: false,
@ -48,7 +47,7 @@ export default class IconButton extends React.PureComponent<Props, States> {
deactivate: false, deactivate: false,
}; };
UNSAFE_componentWillReceiveProps (nextProps: Props) { UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (!nextProps.animate) return; if (!nextProps.animate) return;
if (this.props.active && !nextProps.active) { if (this.props.active && !nextProps.active) {
@ -84,7 +83,7 @@ export default class IconButton extends React.PureComponent<Props, States> {
} }
}; };
render () { render() {
// Hack required for some icons which have an overriden size // Hack required for some icons which have an overriden size
let containerSize = '1.28571429em'; let containerSize = '1.28571429em';
if (this.props.style?.fontSize) { if (this.props.style?.fontSize) {
@ -120,10 +119,7 @@ export default class IconButton extends React.PureComponent<Props, States> {
ariaHidden, ariaHidden,
} = this.props; } = this.props;
const { const { activate, deactivate } = this.state;
activate,
deactivate,
} = this.state;
const classes = classNames(className, 'icon-button', { const classes = classNames(className, 'icon-button', {
active, active,
@ -141,7 +137,12 @@ export default class IconButton extends React.PureComponent<Props, States> {
let contents = ( let contents = (
<React.Fragment> <React.Fragment>
<Icon id={icon} fixedWidth aria-hidden='true' /> {typeof counter !== 'undefined' && <span className='icon-button__counter'><AnimatedNumber value={counter} obfuscate={obfuscateCount} /></span>} <Icon id={icon} fixedWidth aria-hidden='true' />{' '}
{typeof counter !== 'undefined' && (
<span className='icon-button__counter'>
<AnimatedNumber value={counter} obfuscate={obfuscateCount} />
</span>
)}
{this.props.label} {this.props.label}
</React.Fragment> </React.Fragment>
); );
@ -174,5 +175,4 @@ export default class IconButton extends React.PureComponent<Props, States> {
</button> </button>
); );
} }
} }

View File

@ -1,20 +1,25 @@
import React from 'react'; import React from 'react';
import { Icon } from './icon'; import { Icon } from './icon';
const formatNumber = (num: number): number | string => num > 40 ? '40+' : num; const formatNumber = (num: number): number | string => (num > 40 ? '40+' : num);
type Props = { type Props = {
id: string; id: string;
count: number; count: number;
issueBadge: boolean; issueBadge: boolean;
className: string; className: string;
} };
const IconWithBadge: React.FC<Props> = ({ id, count, issueBadge, className }) => ( export const IconWithBadge: React.FC<Props> = ({
id,
count,
issueBadge,
className,
}) => (
<i className='icon-with-badge'> <i className='icon-with-badge'>
<Icon id={id} fixedWidth className={className} /> <Icon id={id} fixedWidth className={className} />
{count > 0 && <i className='icon-with-badge__badge'>{formatNumber(count)}</i>} {count > 0 && (
<i className='icon-with-badge__badge'>{formatNumber(count)}</i>
)}
{issueBadge && <i className='icon-with-badge__issue-badge' />} {issueBadge && <i className='icon-with-badge__issue-badge' />}
</i> </i>
); );
export default IconWithBadge;

View File

@ -1,33 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import Blurhash from './blurhash';
import classNames from 'classnames';
export default class Image extends React.PureComponent {
static propTypes = {
src: PropTypes.string,
srcSet: PropTypes.string,
blurhash: PropTypes.string,
className: PropTypes.string,
};
state = {
loaded: false,
};
handleLoad = () => this.setState({ loaded: true });
render () {
const { src, srcSet, blurhash, className } = this.props;
const { loaded } = this.state;
return (
<div className={classNames('image', { loaded }, className)} role='presentation'>
{blurhash && <Blurhash hash={blurhash} className='image__preview' />}
<img src={src} srcSet={srcSet} alt='' onLoad={this.handleLoad} />
</div>
);
}
}

View File

@ -0,0 +1,33 @@
import React, { useCallback, useState } from 'react';
import { Blurhash } from './blurhash';
import classNames from 'classnames';
type Props = {
src: string;
srcSet?: string;
blurhash?: string;
className?: string;
};
export const Image: React.FC<Props> = ({
src,
srcSet,
blurhash,
className,
}) => {
const [loaded, setLoaded] = useState(false);
const handleLoad = useCallback(() => {
setLoaded(true);
}, [setLoaded]);
return (
<div
className={classNames('image', { loaded }, className)}
role='presentation'
>
{blurhash && <Blurhash hash={blurhash} className='image__preview' />}
<img src={src} srcSet={srcSet} alt='' onLoad={handleLoad} />
</div>
);
};

View File

@ -2,7 +2,7 @@ import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { makeGetAccount } from 'flavours/glitch/selectors'; import { makeGetAccount } from 'flavours/glitch/selectors';
import Avatar from 'flavours/glitch/components/avatar'; import { Avatar } from 'flavours/glitch/components/avatar';
const makeMapStateToProps = () => { const makeMapStateToProps = () => {
const getAccount = makeGetAccount(); const getAccount = makeGetAccount();

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { injectIntl, defineMessages } from 'react-intl'; import { injectIntl, defineMessages } from 'react-intl';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
const messages = defineMessages({ const messages = defineMessages({
load_more: { id: 'status.load_more', defaultMessage: 'Load more' }, load_more: { id: 'status.load_more', defaultMessage: 'Load more' },

View File

@ -2,12 +2,12 @@ import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { is } from 'immutable'; import { is } from 'immutable';
import IconButton from './icon_button'; import { IconButton } from './icon_button';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/initial_state'; import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/initial_state';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import Blurhash from 'flavours/glitch/components/blurhash'; import { Blurhash } from 'flavours/glitch/components/blurhash';
const messages = defineMessages({ const messages = defineMessages({
hidden: { hidden: {

View File

@ -1,12 +0,0 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
const NotSignedInIndicator = () => (
<div className='scrollable scrollable--flex'>
<div className='empty-column-indicator'>
<FormattedMessage id='not_signed_in_indicator.not_signed_in' defaultMessage='You need to sign in to access this resource.' />
</div>
</div>
);
export default NotSignedInIndicator;

View File

@ -0,0 +1,13 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
export const NotSignedInIndicator: React.FC = () => (
<div className='scrollable scrollable--flex'>
<div className='empty-column-indicator'>
<FormattedMessage
id='not_signed_in_indicator.not_signed_in'
defaultMessage='You need to sign in to access this resource.'
/>
</div>
</div>
);

View File

@ -10,7 +10,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import classNames from 'classnames'; import classNames from 'classnames';
const messages = defineMessages({ const messages = defineMessages({

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { removePictureInPicture } from 'flavours/glitch/actions/picture_in_picture'; import { removePictureInPicture } from 'flavours/glitch/actions/picture_in_picture';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';

View File

@ -8,8 +8,8 @@ import Motion from 'flavours/glitch/features/ui/util/optional_motion';
import spring from 'react-motion/lib/spring'; import spring from 'react-motion/lib/spring';
import escapeTextContentForBrowser from 'escape-html'; import escapeTextContentForBrowser from 'escape-html';
import emojify from 'flavours/glitch/features/emoji/emoji'; import emojify from 'flavours/glitch/features/emoji/emoji';
import RelativeTimestamp from './relative_timestamp'; import { RelativeTimestamp } from './relative_timestamp';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
const messages = defineMessages({ const messages = defineMessages({
closed: { closed: {

View File

@ -1,35 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
export default class RadioButton extends React.PureComponent {
static propTypes = {
value: PropTypes.string.isRequired,
checked: PropTypes.bool,
name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
label: PropTypes.node.isRequired,
};
render () {
const { name, value, checked, onChange, label } = this.props;
return (
<label className='radio-button'>
<input
name={name}
type='radio'
value={value}
checked={checked}
onChange={onChange}
/>
<span className={classNames('radio-button__input', { checked })} />
<span>{label}</span>
</label>
);
}
}

View File

@ -0,0 +1,34 @@
import React from 'react';
import classNames from 'classnames';
type Props = {
value: string;
checked: boolean;
name: string;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
label: React.ReactNode;
};
export const RadioButton: React.FC<Props> = ({
name,
value,
checked,
onChange,
label,
}) => {
return (
<label className='radio-button'>
<input
name={name}
type='radio'
value={value}
checked={checked}
onChange={onChange}
/>
<span className={classNames('radio-button__input', { checked })} />
<span>{label}</span>
</label>
);
};

View File

@ -4,20 +4,50 @@ import { injectIntl, defineMessages, InjectedIntl } from 'react-intl';
const messages = defineMessages({ const messages = defineMessages({
today: { id: 'relative_time.today', defaultMessage: 'today' }, today: { id: 'relative_time.today', defaultMessage: 'today' },
just_now: { id: 'relative_time.just_now', defaultMessage: 'now' }, just_now: { id: 'relative_time.just_now', defaultMessage: 'now' },
just_now_full: { id: 'relative_time.full.just_now', defaultMessage: 'just now' }, just_now_full: {
id: 'relative_time.full.just_now',
defaultMessage: 'just now',
},
seconds: { id: 'relative_time.seconds', defaultMessage: '{number}s' }, seconds: { id: 'relative_time.seconds', defaultMessage: '{number}s' },
seconds_full: { id: 'relative_time.full.seconds', defaultMessage: '{number, plural, one {# second} other {# seconds}} ago' }, seconds_full: {
id: 'relative_time.full.seconds',
defaultMessage: '{number, plural, one {# second} other {# seconds}} ago',
},
minutes: { id: 'relative_time.minutes', defaultMessage: '{number}m' }, minutes: { id: 'relative_time.minutes', defaultMessage: '{number}m' },
minutes_full: { id: 'relative_time.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}} ago' }, minutes_full: {
id: 'relative_time.full.minutes',
defaultMessage: '{number, plural, one {# minute} other {# minutes}} ago',
},
hours: { id: 'relative_time.hours', defaultMessage: '{number}h' }, hours: { id: 'relative_time.hours', defaultMessage: '{number}h' },
hours_full: { id: 'relative_time.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}} ago' }, hours_full: {
id: 'relative_time.full.hours',
defaultMessage: '{number, plural, one {# hour} other {# hours}} ago',
},
days: { id: 'relative_time.days', defaultMessage: '{number}d' }, days: { id: 'relative_time.days', defaultMessage: '{number}d' },
days_full: { id: 'relative_time.full.days', defaultMessage: '{number, plural, one {# day} other {# days}} ago' }, days_full: {
moments_remaining: { id: 'time_remaining.moments', defaultMessage: 'Moments remaining' }, id: 'relative_time.full.days',
seconds_remaining: { id: 'time_remaining.seconds', defaultMessage: '{number, plural, one {# second} other {# seconds}} left' }, defaultMessage: '{number, plural, one {# day} other {# days}} ago',
minutes_remaining: { id: 'time_remaining.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}} left' }, },
hours_remaining: { id: 'time_remaining.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}} left' }, moments_remaining: {
days_remaining: { id: 'time_remaining.days', defaultMessage: '{number, plural, one {# day} other {# days}} left' }, id: 'time_remaining.moments',
defaultMessage: 'Moments remaining',
},
seconds_remaining: {
id: 'time_remaining.seconds',
defaultMessage: '{number, plural, one {# second} other {# seconds}} left',
},
minutes_remaining: {
id: 'time_remaining.minutes',
defaultMessage: '{number, plural, one {# minute} other {# minutes}} left',
},
hours_remaining: {
id: 'time_remaining.hours',
defaultMessage: '{number, plural, one {# hour} other {# hours}} left',
},
days_remaining: {
id: 'time_remaining.days',
defaultMessage: '{number, plural, one {# day} other {# days}} left',
},
}); });
const dateFormatOptions = { const dateFormatOptions = {
@ -70,7 +100,14 @@ const getUnitDelay = (units: string) => {
} }
}; };
export const timeAgoString = (intl: InjectedIntl, date: Date, now: number, year: number, timeGiven: boolean, short?: boolean) => { export const timeAgoString = (
intl: InjectedIntl,
date: Date,
now: number,
year: number,
timeGiven: boolean,
short?: boolean
) => {
const delta = now - date.getTime(); const delta = now - date.getTime();
let relativeTime; let relativeTime;
@ -78,27 +115,49 @@ export const timeAgoString = (intl: InjectedIntl, date: Date, now: number, year:
if (delta < DAY && !timeGiven) { if (delta < DAY && !timeGiven) {
relativeTime = intl.formatMessage(messages.today); relativeTime = intl.formatMessage(messages.today);
} else if (delta < 10 * SECOND) { } else if (delta < 10 * SECOND) {
relativeTime = intl.formatMessage(short ? messages.just_now : messages.just_now_full); relativeTime = intl.formatMessage(
short ? messages.just_now : messages.just_now_full
);
} else if (delta < 7 * DAY) { } else if (delta < 7 * DAY) {
if (delta < MINUTE) { if (delta < MINUTE) {
relativeTime = intl.formatMessage(short ? messages.seconds : messages.seconds_full, { number: Math.floor(delta / SECOND) }); relativeTime = intl.formatMessage(
short ? messages.seconds : messages.seconds_full,
{ number: Math.floor(delta / SECOND) }
);
} else if (delta < HOUR) { } else if (delta < HOUR) {
relativeTime = intl.formatMessage(short ? messages.minutes : messages.minutes_full, { number: Math.floor(delta / MINUTE) }); relativeTime = intl.formatMessage(
short ? messages.minutes : messages.minutes_full,
{ number: Math.floor(delta / MINUTE) }
);
} else if (delta < DAY) { } else if (delta < DAY) {
relativeTime = intl.formatMessage(short ? messages.hours : messages.hours_full, { number: Math.floor(delta / HOUR) }); relativeTime = intl.formatMessage(
short ? messages.hours : messages.hours_full,
{ number: Math.floor(delta / HOUR) }
);
} else { } else {
relativeTime = intl.formatMessage(short ? messages.days : messages.days_full, { number: Math.floor(delta / DAY) }); relativeTime = intl.formatMessage(
short ? messages.days : messages.days_full,
{ number: Math.floor(delta / DAY) }
);
} }
} else if (date.getFullYear() === year) { } else if (date.getFullYear() === year) {
relativeTime = intl.formatDate(date, shortDateFormatOptions); relativeTime = intl.formatDate(date, shortDateFormatOptions);
} else { } else {
relativeTime = intl.formatDate(date, { ...shortDateFormatOptions, year: 'numeric' }); relativeTime = intl.formatDate(date, {
...shortDateFormatOptions,
year: 'numeric',
});
} }
return relativeTime; return relativeTime;
}; };
const timeRemainingString = (intl: InjectedIntl, date: Date, now: number, timeGiven = true) => { const timeRemainingString = (
intl: InjectedIntl,
date: Date,
now: number,
timeGiven = true
) => {
const delta = date.getTime() - now; const delta = date.getTime() - now;
let relativeTime; let relativeTime;
@ -108,13 +167,21 @@ const timeRemainingString = (intl: InjectedIntl, date: Date, now: number, timeGi
} else if (delta < 10 * SECOND) { } else if (delta < 10 * SECOND) {
relativeTime = intl.formatMessage(messages.moments_remaining); relativeTime = intl.formatMessage(messages.moments_remaining);
} else if (delta < MINUTE) { } else if (delta < MINUTE) {
relativeTime = intl.formatMessage(messages.seconds_remaining, { number: Math.floor(delta / SECOND) }); relativeTime = intl.formatMessage(messages.seconds_remaining, {
number: Math.floor(delta / SECOND),
});
} else if (delta < HOUR) { } else if (delta < HOUR) {
relativeTime = intl.formatMessage(messages.minutes_remaining, { number: Math.floor(delta / MINUTE) }); relativeTime = intl.formatMessage(messages.minutes_remaining, {
number: Math.floor(delta / MINUTE),
});
} else if (delta < DAY) { } else if (delta < DAY) {
relativeTime = intl.formatMessage(messages.hours_remaining, { number: Math.floor(delta / HOUR) }); relativeTime = intl.formatMessage(messages.hours_remaining, {
number: Math.floor(delta / HOUR),
});
} else { } else {
relativeTime = intl.formatMessage(messages.days_remaining, { number: Math.floor(delta / DAY) }); relativeTime = intl.formatMessage(messages.days_remaining, {
number: Math.floor(delta / DAY),
});
} }
return relativeTime; return relativeTime;
@ -126,78 +193,88 @@ type Props = {
year: number; year: number;
futureDate?: boolean; futureDate?: boolean;
short?: boolean; short?: boolean;
} };
type States = { type States = {
now: number; now: number;
} };
class RelativeTimestamp extends React.Component<Props, States> { class RelativeTimestamp extends React.Component<Props, States> {
state = { state = {
now: this.props.intl.now(), now: this.props.intl.now(),
}; };
static defaultProps = { static defaultProps = {
year: (new Date()).getFullYear(), year: new Date().getFullYear(),
short: true, short: true,
}; };
_timer: number | undefined; _timer: number | undefined;
shouldComponentUpdate (nextProps: Props, nextState: States) { shouldComponentUpdate(nextProps: Props, nextState: States) {
// As of right now the locale doesn't change without a new page load, // As of right now the locale doesn't change without a new page load,
// but we might as well check in case that ever changes. // but we might as well check in case that ever changes.
return this.props.timestamp !== nextProps.timestamp || return (
this.props.timestamp !== nextProps.timestamp ||
this.props.intl.locale !== nextProps.intl.locale || this.props.intl.locale !== nextProps.intl.locale ||
this.state.now !== nextState.now; this.state.now !== nextState.now
);
} }
UNSAFE_componentWillReceiveProps (nextProps: Props) { UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (this.props.timestamp !== nextProps.timestamp) { if (this.props.timestamp !== nextProps.timestamp) {
this.setState({ now: this.props.intl.now() }); this.setState({ now: this.props.intl.now() });
} }
} }
componentDidMount () { componentDidMount() {
this._scheduleNextUpdate(this.props, this.state); this._scheduleNextUpdate(this.props, this.state);
} }
UNSAFE_componentWillUpdate (nextProps: Props, nextState: States) { UNSAFE_componentWillUpdate(nextProps: Props, nextState: States) {
this._scheduleNextUpdate(nextProps, nextState); this._scheduleNextUpdate(nextProps, nextState);
} }
componentWillUnmount () { componentWillUnmount() {
window.clearTimeout(this._timer); window.clearTimeout(this._timer);
} }
_scheduleNextUpdate (props: Props, state: States) { _scheduleNextUpdate(props: Props, state: States) {
window.clearTimeout(this._timer); window.clearTimeout(this._timer);
const { timestamp } = props; const { timestamp } = props;
const delta = (new Date(timestamp)).getTime() - state.now; const delta = new Date(timestamp).getTime() - state.now;
const unitDelay = getUnitDelay(selectUnits(delta)); const unitDelay = getUnitDelay(selectUnits(delta));
const unitRemainder = Math.abs(delta % unitDelay); const unitRemainder = Math.abs(delta % unitDelay);
const updateInterval = 1000 * 10; const updateInterval = 1000 * 10;
const delay = delta < 0 ? Math.max(updateInterval, unitDelay - unitRemainder) : Math.max(updateInterval, unitRemainder); const delay =
delta < 0
? Math.max(updateInterval, unitDelay - unitRemainder)
: Math.max(updateInterval, unitRemainder);
this._timer = window.setTimeout(() => { this._timer = window.setTimeout(() => {
this.setState({ now: this.props.intl.now() }); this.setState({ now: this.props.intl.now() });
}, delay); }, delay);
} }
render () { render() {
const { timestamp, intl, year, futureDate, short } = this.props; const { timestamp, intl, year, futureDate, short } = this.props;
const timeGiven = timestamp.includes('T'); const timeGiven = timestamp.includes('T');
const date = new Date(timestamp); const date = new Date(timestamp);
const relativeTime = futureDate ? timeRemainingString(intl, date, this.state.now, timeGiven) : timeAgoString(intl, date, this.state.now, year, timeGiven, short); const relativeTime = futureDate
? timeRemainingString(intl, date, this.state.now, timeGiven)
: timeAgoString(intl, date, this.state.now, year, timeGiven, short);
return ( return (
<time dateTime={timestamp} title={intl.formatDate(date, dateFormatOptions)}> <time
dateTime={timestamp}
title={intl.formatDate(date, dateFormatOptions)}
>
{relativeTime} {relativeTime}
</time> </time>
); );
} }
} }
export default injectIntl(RelativeTimestamp); const RelativeTimestampWithIntl = injectIntl(RelativeTimestamp);
export { RelativeTimestampWithIntl as RelativeTimestamp };

View File

@ -8,6 +8,7 @@ import IntersectionObserverWrapper from 'flavours/glitch/features/ui/util/inters
import { throttle } from 'lodash'; import { throttle } from 'lodash';
import { List as ImmutableList } from 'immutable'; import { List as ImmutableList } from 'immutable';
import classNames from 'classnames'; import classNames from 'classnames';
import { supportsPassiveEvents } from 'detect-passive-events';
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen'; import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen';
import LoadingIndicator from './loading_indicator'; import LoadingIndicator from './loading_indicator';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -236,10 +237,10 @@ class ScrollableList extends PureComponent {
attachScrollListener () { attachScrollListener () {
if (this.props.bindToDocument) { if (this.props.bindToDocument) {
document.addEventListener('scroll', this.handleScroll); document.addEventListener('scroll', this.handleScroll);
document.addEventListener('wheel', this.handleWheel); document.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : undefined);
} else { } else {
this.node.addEventListener('scroll', this.handleScroll); this.node.addEventListener('scroll', this.handleScroll);
this.node.addEventListener('wheel', this.handleWheel); this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : undefined);
} }
} }

View File

@ -7,7 +7,7 @@ import ShortNumber from 'flavours/glitch/components/short_number';
import Skeleton from 'flavours/glitch/components/skeleton'; import Skeleton from 'flavours/glitch/components/skeleton';
import Account from 'flavours/glitch/containers/account_container'; import Account from 'flavours/glitch/containers/account_container';
import { domain } from 'flavours/glitch/initial_state'; import { domain } from 'flavours/glitch/initial_state';
import Image from 'flavours/glitch/components/image'; import { Image } from 'flavours/glitch/components/image';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
const messages = defineMessages({ const messages = defineMessages({

View File

@ -1,12 +1,12 @@
import React from 'react'; import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import IconButton from './icon_button'; import { IconButton } from './icon_button';
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { me } from 'flavours/glitch/initial_state'; import { me } from 'flavours/glitch/initial_state';
import RelativeTimestamp from './relative_timestamp'; import { RelativeTimestamp } from './relative_timestamp';
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links'; import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links';
import classNames from 'classnames'; import classNames from 'classnames';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';

View File

@ -5,7 +5,7 @@ import { FormattedMessage, injectIntl } from 'react-intl';
import Permalink from './permalink'; import Permalink from './permalink';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import classnames from 'classnames'; import classnames from 'classnames';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state'; import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state';
import { decode as decodeIDNA } from 'flavours/glitch/utils/idna'; import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';

View File

@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
// Mastodon imports. // Mastodon imports.
import Avatar from './avatar'; import { Avatar } from './avatar';
import AvatarOverlay from './avatar_overlay'; import AvatarOverlay from './avatar_overlay';
import DisplayName from './display_name'; import DisplayName from './display_name';

View File

@ -5,9 +5,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
// Mastodon imports. // Mastodon imports.
import IconButton from './icon_button'; import { IconButton } from './icon_button';
import VisibilityIcon from './status_visibility_icon'; import VisibilityIcon from './status_visibility_icon';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { languages } from 'flavours/glitch/initial_state'; import { languages } from 'flavours/glitch/initial_state';
// Messages for use with internationalization stuff. // Messages for use with internationalization stuff.

View File

@ -3,7 +3,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { me } from 'flavours/glitch/initial_state'; import { me } from 'flavours/glitch/initial_state';
export default class StatusPrepend extends React.PureComponent { export default class StatusPrepend extends React.PureComponent {

View File

@ -3,7 +3,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
const messages = defineMessages({ const messages = defineMessages({
public: { id: 'privacy.public.short', defaultMessage: 'Public' }, public: { id: 'privacy.public.short', defaultMessage: 'Public' },

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { store } from 'flavours/glitch/store/configureStore'; import { store } from 'flavours/glitch/store';
import { hydrateStore } from 'flavours/glitch/actions/store'; import { hydrateStore } from 'flavours/glitch/actions/store';
import { IntlProvider, addLocaleData } from 'react-intl'; import { IntlProvider, addLocaleData } from 'react-intl';
import { getLocale } from 'mastodon/locales'; import { getLocale } from 'mastodon/locales';

View File

@ -2,7 +2,7 @@ import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { blockDomain, unblockDomain } from '../actions/domain_blocks'; import { blockDomain, unblockDomain } from '../actions/domain_blocks';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import Domain from '../components/domain'; import { Domain } from '../components/domain';
import { openModal } from '../actions/modal'; import { openModal } from '../actions/modal';
const messages = defineMessages({ const messages = defineMessages({

View File

@ -5,7 +5,7 @@ import { IntlProvider, addLocaleData } from 'react-intl';
import { Provider as ReduxProvider } from 'react-redux'; import { Provider as ReduxProvider } from 'react-redux';
import { BrowserRouter, Route } from 'react-router-dom'; import { BrowserRouter, Route } from 'react-router-dom';
import { ScrollContext } from 'react-router-scroll-4'; import { ScrollContext } from 'react-router-scroll-4';
import { store } from 'flavours/glitch/store/configureStore'; import { store } from 'flavours/glitch/store';
import UI from 'flavours/glitch/features/ui'; import UI from 'flavours/glitch/features/ui';
import { fetchCustomEmojis } from 'flavours/glitch/actions/custom_emojis'; import { fetchCustomEmojis } from 'flavours/glitch/actions/custom_emojis';
import { hydrateStore } from 'flavours/glitch/actions/store'; import { hydrateStore } from 'flavours/glitch/actions/store';

View File

@ -9,9 +9,9 @@ import { Helmet } from 'react-helmet';
import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'flavours/glitch/actions/server'; import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'flavours/glitch/actions/server';
import Account from 'flavours/glitch/containers/account_container'; import Account from 'flavours/glitch/containers/account_container';
import Skeleton from 'flavours/glitch/components/skeleton'; import Skeleton from 'flavours/glitch/components/skeleton';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import classNames from 'classnames'; import classNames from 'classnames';
import Image from 'flavours/glitch/components/image'; import { Image } from 'flavours/glitch/components/image';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.about', defaultMessage: 'About' }, title: { id: 'column.about', defaultMessage: 'About' },

View File

@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import Textarea from 'react-textarea-autosize'; import Textarea from 'react-textarea-autosize';
const messages = defineMessages({ const messages = defineMessages({

View File

@ -2,7 +2,7 @@ import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { FormattedMessage, FormattedNumber } from 'react-intl'; import { FormattedMessage, FormattedNumber } from 'react-intl';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
class ActionBar extends React.PureComponent { class ActionBar extends React.PureComponent {

View File

@ -2,7 +2,7 @@ import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
export default class FollowRequestNote extends ImmutablePureComponent { export default class FollowRequestNote extends ImmutablePureComponent {

View File

@ -6,9 +6,9 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { autoPlayGif, me, domain } from 'flavours/glitch/initial_state'; import { autoPlayGif, me, domain } from 'flavours/glitch/initial_state';
import { preferencesLink, profileLink, accountAdminLink } from 'flavours/glitch/utils/backend_links'; import { preferencesLink, profileLink, accountAdminLink } from 'flavours/glitch/utils/backend_links';
import classNames from 'classnames'; import classNames from 'classnames';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import IconButton from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import Avatar from 'flavours/glitch/components/avatar'; import { Avatar } from 'flavours/glitch/components/avatar';
import Button from 'flavours/glitch/components/button'; import Button from 'flavours/glitch/components/button';
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
import AccountNoteContainer from '../containers/account_note_container'; import AccountNoteContainer from '../containers/account_note_container';

View File

@ -1,6 +1,6 @@
import Blurhash from 'flavours/glitch/components/blurhash'; import { Blurhash } from 'flavours/glitch/components/blurhash';
import classNames from 'classnames'; import classNames from 'classnames';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/initial_state'; import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/initial_state';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';

View File

@ -5,7 +5,7 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import AvatarOverlay from '../../../components/avatar_overlay'; import AvatarOverlay from '../../../components/avatar_overlay';
import DisplayName from '../../../components/display_name'; import DisplayName from '../../../components/display_name';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
export default class MovedNote extends ImmutablePureComponent { export default class MovedNote extends ImmutablePureComponent {

View File

@ -2,12 +2,12 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import { formatTime, getPointerPosition, fileNameFromURL } from 'flavours/glitch/features/video'; import { formatTime, getPointerPosition, fileNameFromURL } from 'flavours/glitch/features/video';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import classNames from 'classnames'; import classNames from 'classnames';
import { throttle, debounce } from 'lodash'; import { throttle, debounce } from 'lodash';
import Visualizer from './visualizer'; import Visualizer from './visualizer';
import { displayMedia, useBlurhash } from 'flavours/glitch/initial_state'; import { displayMedia, useBlurhash } from 'flavours/glitch/initial_state';
import Blurhash from 'flavours/glitch/components/blurhash'; import { Blurhash } from 'flavours/glitch/components/blurhash';
import { is } from 'immutable'; import { is } from 'immutable';
const messages = defineMessages({ const messages = defineMessages({

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import Avatar from 'flavours/glitch/components/avatar'; import { Avatar } from 'flavours/glitch/components/avatar';
import DisplayName from 'flavours/glitch/components/display_name'; import DisplayName from 'flavours/glitch/components/display_name';
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';

View File

@ -5,7 +5,7 @@ import React from 'react';
import Overlay from 'react-overlays/Overlay'; import Overlay from 'react-overlays/Overlay';
// Components. // Components.
import IconButton from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import DropdownMenu from './dropdown_menu'; import DropdownMenu from './dropdown_menu';
// The component. // The component.

View File

@ -4,7 +4,7 @@ import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
// Components. // Components.
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
// Utils. // Utils.
import { withPassive } from 'flavours/glitch/utils/dom_helpers'; import { withPassive } from 'flavours/glitch/utils/dom_helpers';

View File

@ -7,7 +7,7 @@ import { Link } from 'react-router-dom';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
// Components. // Components.
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
// Utils. // Utils.
import { conditionalRender } from 'flavours/glitch/utils/react_helpers'; import { conditionalRender } from 'flavours/glitch/utils/react_helpers';

View File

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ActionBar from './action_bar'; import ActionBar from './action_bar';
import Avatar from 'flavours/glitch/components/avatar'; import { Avatar } from 'flavours/glitch/components/avatar';
import Permalink from 'flavours/glitch/components/permalink'; import Permalink from 'flavours/glitch/components/permalink';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';

View File

@ -7,7 +7,7 @@ import Toggle from 'react-toggle';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
// Components. // Components.
import IconButton from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import TextIconButton from './text_icon_button'; import TextIconButton from './text_icon_button';
import DropdownContainer from '../containers/dropdown_container'; import DropdownContainer from '../containers/dropdown_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';

View File

@ -3,8 +3,8 @@ import PropTypes from 'prop-types';
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 { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import IconButton from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import AutosuggestInput from 'flavours/glitch/components/autosuggest_input'; import AutosuggestInput from 'flavours/glitch/components/autosuggest_input';
import classNames from 'classnames'; import classNames from 'classnames';
import { pollLimits } from 'flavours/glitch/initial_state'; import { pollLimits } from 'flavours/glitch/initial_state';

View File

@ -8,7 +8,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
// Components. // Components.
import Button from 'flavours/glitch/components/button'; import Button from 'flavours/glitch/components/button';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
// Utils. // Utils.
import { maxChars } from 'flavours/glitch/initial_state'; import { maxChars } from 'flavours/glitch/initial_state';

View File

@ -7,8 +7,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
// Components. // Components.
import AccountContainer from 'flavours/glitch/containers/account_container'; import AccountContainer from 'flavours/glitch/containers/account_container';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import IconButton from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import AttachmentList from 'flavours/glitch/components/attachment_list'; import AttachmentList from 'flavours/glitch/components/attachment_list';
// Messages. // Messages.

View File

@ -7,8 +7,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
// Components. // Components.
import AccountContainer from 'flavours/glitch/containers/account_container'; import AccountContainer from 'flavours/glitch/containers/account_container';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import IconButton from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import AttachmentList from 'flavours/glitch/components/attachment_list'; import AttachmentList from 'flavours/glitch/components/attachment_list';
// Messages. // Messages.

View File

@ -9,7 +9,7 @@ import {
import Overlay from 'react-overlays/Overlay'; import Overlay from 'react-overlays/Overlay';
// Components. // Components.
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
// Utils. // Utils.
import { focusRoot } from 'flavours/glitch/utils/dom_helpers'; import { focusRoot } from 'flavours/glitch/utils/dom_helpers';

View File

@ -6,7 +6,7 @@ import AccountContainer from 'flavours/glitch/containers/account_container';
import StatusContainer from 'flavours/glitch/containers/status_container'; import StatusContainer from 'flavours/glitch/containers/status_container';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag'; import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { searchEnabled } from 'flavours/glitch/initial_state'; import { searchEnabled } from 'flavours/glitch/initial_state';
import LoadMore from 'flavours/glitch/components/load_more'; import LoadMore from 'flavours/glitch/components/load_more';

View File

@ -6,7 +6,7 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
// Components. // Components.
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
// Messages. // Messages.
const messages = defineMessages({ const messages = defineMessages({

View File

@ -5,7 +5,7 @@ import Motion from '../../ui/util/optional_motion';
import spring from 'react-motion/lib/spring'; import spring from 'react-motion/lib/spring';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
export default class Upload extends ImmutablePureComponent { export default class Upload extends ImmutablePureComponent {

View File

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Motion from '../../ui/util/optional_motion'; import Motion from '../../ui/util/optional_motion';
import spring from 'react-motion/lib/spring'; import spring from 'react-motion/lib/spring';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
export default class UploadProgress extends React.PureComponent { export default class UploadProgress extends React.PureComponent {

View File

@ -72,6 +72,7 @@ const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({
}, },
onPickEmoji: emoji => { onPickEmoji: emoji => {
// eslint-disable-next-line react-hooks/rules-of-hooks -- this is not a react hook
dispatch(useEmoji(emoji)); dispatch(useEmoji(emoji));
if (onPickEmoji) { if (onPickEmoji) {

View File

@ -26,6 +26,7 @@ const mapDispatchToProps = dispatch => ({
}, },
onClose (value) { onClose (value) {
// eslint-disable-next-line react-hooks/rules-of-hooks -- this is not a react hook
dispatch(useLanguage(value)); dispatch(useLanguage(value));
}, },

View File

@ -8,8 +8,8 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
import AvatarComposite from 'flavours/glitch/components/avatar_composite'; import AvatarComposite from 'flavours/glitch/components/avatar_composite';
import Permalink from 'flavours/glitch/components/permalink'; import Permalink from 'flavours/glitch/components/permalink';
import IconButton from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp';
import { HotKeys } from 'react-hotkeys'; import { HotKeys } from 'react-hotkeys';
import { autoPlayGif } from 'flavours/glitch/initial_state'; import { autoPlayGif } from 'flavours/glitch/initial_state';
import classNames from 'classnames'; import classNames from 'classnames';

View File

@ -4,10 +4,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { makeGetAccount } from 'flavours/glitch/selectors'; import { makeGetAccount } from 'flavours/glitch/selectors';
import Avatar from 'flavours/glitch/components/avatar'; import { Avatar } from 'flavours/glitch/components/avatar';
import DisplayName from 'flavours/glitch/components/display_name'; import DisplayName from 'flavours/glitch/components/display_name';
import Permalink from 'flavours/glitch/components/permalink'; import Permalink from 'flavours/glitch/components/permalink';
import IconButton from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import Button from 'flavours/glitch/components/button'; import Button from 'flavours/glitch/components/button';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import { autoPlayGif, me, unfollowModal } from 'flavours/glitch/initial_state'; import { autoPlayGif, me, unfollowModal } from 'flavours/glitch/initial_state';

View File

@ -9,7 +9,7 @@ import { addColumn, removeColumn, moveColumn, changeColumnParams } from 'flavour
import { fetchDirectory, expandDirectory } from 'flavours/glitch/actions/directory'; import { fetchDirectory, expandDirectory } from 'flavours/glitch/actions/directory';
import { List as ImmutableList } from 'immutable'; import { List as ImmutableList } from 'immutable';
import AccountCard from './components/account_card'; import AccountCard from './components/account_card';
import RadioButton from 'flavours/glitch/components/radio_button'; import { RadioButton } from 'flavours/glitch/components/radio_button';
import LoadMore from 'flavours/glitch/components/load_more'; import LoadMore from 'flavours/glitch/components/load_more';
import ScrollContainer from 'flavours/glitch/containers/scroll_container'; import ScrollContainer from 'flavours/glitch/containers/scroll_container';
import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; import LoadingIndicator from 'flavours/glitch/components/loading_indicator';

View File

@ -1,3 +1,5 @@
/* eslint-disable import/no-commonjs --
We need to use CommonJS here due to preval */
// @preval // @preval
// http://www.unicode.org/Public/emoji/5.0/emoji-test.txt // http://www.unicode.org/Public/emoji/5.0/emoji-test.txt
// This file contains the compressed version of the emoji data from // This file contains the compressed version of the emoji data from

View File

@ -1,8 +1,10 @@
// The output of this module is designed to mimic emoji-mart's // The output of this module is designed to mimic emoji-mart's
// "data" object, such that we can use it for a light version of emoji-mart's // "data" object, such that we can use it for a light version of emoji-mart's
// emojiIndex.search functionality. // emojiIndex.search functionality.
const { unicodeToUnifiedName } = require('./unicode_to_unified_name'); import { unicodeToUnifiedName } from './unicode_to_unified_name';
const [ shortCodesToEmojiData, skins, categories, short_names ] = require('./emoji_compressed'); import emojiCompressed from './emoji_compressed';
const [ shortCodesToEmojiData, skins, categories, short_names ] = emojiCompressed;
const emojis = {}; const emojis = {};
@ -33,7 +35,7 @@ Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
}; };
}); });
module.exports = { export {
emojis, emojis,
skins, skins,
categories, categories,

View File

@ -1,7 +1,7 @@
// This code is largely borrowed from: // This code is largely borrowed from:
// https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/emoji-index.js // https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/emoji-index.js
import data from './emoji_mart_data_light'; import * as data from './emoji_mart_data_light';
import { getData, getSanitizedData, uniq, intersect } from './emoji_utils'; import { getData, getSanitizedData, uniq, intersect } from './emoji_utils';
let originalPool = {}; let originalPool = {};

View File

@ -2,14 +2,17 @@
// (i.e. the svg filename) and a shortCode intended to be shown // (i.e. the svg filename) and a shortCode intended to be shown
// as a "title" attribute in an HTML element (aka tooltip). // as a "title" attribute in an HTML element (aka tooltip).
import emojiCompressed from './emoji_compressed';
import { unicodeToFilename } from './unicode_to_filename';
const [ const [
shortCodesToEmojiData, shortCodesToEmojiData,
skins, // eslint-disable-line @typescript-eslint/no-unused-vars _skins,
categories, // eslint-disable-line @typescript-eslint/no-unused-vars _categories,
short_names, // eslint-disable-line @typescript-eslint/no-unused-vars _short_names,
emojisWithoutShortCodes, emojisWithoutShortCodes,
] = require('./emoji_compressed'); ] = emojiCompressed;
const { unicodeToFilename } = require('./unicode_to_filename');
// decompress // decompress
const unicodeMapping = {}; const unicodeMapping = {};
@ -32,4 +35,4 @@ Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
}); });
emojisWithoutShortCodes.forEach(emojiMapData => processEmojiMapData(emojiMapData)); emojisWithoutShortCodes.forEach(emojiMapData => processEmojiMapData(emojiMapData));
module.exports = unicodeMapping; export default unicodeMapping;

View File

@ -1,7 +1,7 @@
// This code is largely borrowed from: // This code is largely borrowed from:
// https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/index.js // https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/index.js
import data from './emoji_mart_data_light'; import * as data from './emoji_mart_data_light';
const buildSearch = (data) => { const buildSearch = (data) => {
const search = []; const search = [];

View File

@ -1,3 +1,6 @@
/* eslint-disable import/no-commonjs --
We need to use CommonJS here as its imported into a preval file (`emoji_compressed.js`) */
// taken from: // taken from:
// https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866 // https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866
exports.unicodeToFilename = (str) => { exports.unicodeToFilename = (str) => {

View File

@ -1,3 +1,6 @@
/* eslint-disable import/no-commonjs --
We need to use CommonJS here as its imported into a preval file (`emoji_compressed.js`) */
function padLeft(str, num) { function padLeft(str, num) {
while (str.length < num) { while (str.length < num) {
str = '0' + str; str = '0' + str;

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Blurhash from 'flavours/glitch/components/blurhash'; import { Blurhash } from 'flavours/glitch/components/blurhash';
import { accountsCountRenderer } from 'flavours/glitch/components/hashtag'; import { accountsCountRenderer } from 'flavours/glitch/components/hashtag';
import ShortNumber from 'flavours/glitch/components/short_number'; import ShortNumber from 'flavours/glitch/components/short_number';
import Skeleton from 'flavours/glitch/components/skeleton'; import Skeleton from 'flavours/glitch/components/skeleton';

View File

@ -5,7 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import ColumnHeader from 'flavours/glitch/components/column_header'; import ColumnHeader from 'flavours/glitch/components/column_header';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { fetchFavourites } from 'flavours/glitch/actions/interactions'; import { fetchFavourites } from 'flavours/glitch/actions/interactions';
import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
import ScrollableList from 'flavours/glitch/components/scrollable_list'; import ScrollableList from 'flavours/glitch/components/scrollable_list';

View File

@ -4,7 +4,7 @@ import { connect } from 'react-redux';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { toServerSideType } from 'flavours/glitch/utils/filters'; import { toServerSideType } from 'flavours/glitch/utils/filters';
import { loupeIcon, deleteIcon } from 'flavours/glitch/utils/icons'; import { loupeIcon, deleteIcon } from 'flavours/glitch/utils/icons';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import fuzzysort from 'fuzzysort'; import fuzzysort from 'fuzzysort';
const messages = defineMessages({ const messages = defineMessages({

View File

@ -4,10 +4,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { makeGetAccount } from 'flavours/glitch/selectors'; import { makeGetAccount } from 'flavours/glitch/selectors';
import Avatar from 'flavours/glitch/components/avatar'; import { Avatar } from 'flavours/glitch/components/avatar';
import DisplayName from 'flavours/glitch/components/display_name'; import DisplayName from 'flavours/glitch/components/display_name';
import Permalink from 'flavours/glitch/components/permalink'; import Permalink from 'flavours/glitch/components/permalink';
import IconButton from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import { injectIntl, defineMessages } from 'react-intl'; import { injectIntl, defineMessages } from 'react-intl';
import { followAccount, unfollowAccount } from 'flavours/glitch/actions/accounts'; import { followAccount, unfollowAccount } from 'flavours/glitch/actions/accounts';

View File

@ -2,9 +2,9 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import Permalink from 'flavours/glitch/components/permalink'; import Permalink from 'flavours/glitch/components/permalink';
import Avatar from 'flavours/glitch/components/avatar'; import { Avatar } from 'flavours/glitch/components/avatar';
import DisplayName from 'flavours/glitch/components/display_name'; import DisplayName from 'flavours/glitch/components/display_name';
import IconButton from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';

View File

@ -3,15 +3,15 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import ReactSwipeableViews from 'react-swipeable-views'; import ReactSwipeableViews from 'react-swipeable-views';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import IconButton from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl';
import { autoPlayGif, reduceMotion, disableSwiping, mascot } from 'flavours/glitch/initial_state'; import { autoPlayGif, reduceMotion, disableSwiping, mascot } from 'flavours/glitch/initial_state';
import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg'; import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg';
import unicodeMapping from 'flavours/glitch/features/emoji/emoji_unicode_mapping_light'; import unicodeMapping from 'flavours/glitch/features/emoji/emoji_unicode_mapping_light';
import classNames from 'classnames'; import classNames from 'classnames';
import EmojiPickerDropdown from 'flavours/glitch/features/compose/containers/emoji_picker_dropdown_container'; import EmojiPickerDropdown from 'flavours/glitch/features/compose/containers/emoji_picker_dropdown_container';
import AnimatedNumber from 'flavours/glitch/components/animated_number'; import { AnimatedNumber } from 'flavours/glitch/components/animated_number';
import TransitionMotion from 'react-motion/lib/TransitionMotion'; import TransitionMotion from 'react-motion/lib/TransitionMotion';
import spring from 'react-motion/lib/spring'; import spring from 'react-motion/lib/spring';
import { assetHost } from 'flavours/glitch/utils/config'; import { assetHost } from 'flavours/glitch/utils/config';

View File

@ -12,7 +12,7 @@ import { connectHashtagStream } from 'flavours/glitch/actions/streaming';
import { injectIntl, FormattedMessage, defineMessages } from 'react-intl'; import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { fetchHashtag, followHashtag, unfollowHashtag } from 'flavours/glitch/actions/tags'; import { fetchHashtag, followHashtag, unfollowHashtag } from 'flavours/glitch/actions/tags';
import Icon from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import classNames from 'classnames'; import classNames from 'classnames';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';

View File

@ -12,8 +12,8 @@ import { Link } from 'react-router-dom';
import { fetchAnnouncements, toggleShowAnnouncements } from 'flavours/glitch/actions/announcements'; import { fetchAnnouncements, toggleShowAnnouncements } from 'flavours/glitch/actions/announcements';
import AnnouncementsContainer from 'flavours/glitch/features/getting_started/containers/announcements_container'; import AnnouncementsContainer from 'flavours/glitch/features/getting_started/containers/announcements_container';
import classNames from 'classnames'; import classNames from 'classnames';
import IconWithBadge from 'flavours/glitch/components/icon_with_badge'; import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge';
import NotSignedInIndicator from 'flavours/glitch/components/not_signed_in_indicator'; import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
const messages = defineMessages({ const messages = defineMessages({

Some files were not shown because too many files have changed in this diff Show More