From a65d2d10458fcb6c1c36fa6dd52b8f64d12ce50d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E3=81=9F=E3=81=84=E3=81=A1=20=E3=81=B2?=
Date: Mon, 8 May 2023 18:12:44 +0900
Subject: [PATCH 04/13] Rewrite Image component as function component (#24893)
---
app/javascript/mastodon/components/image.jsx | 33 --------------------
app/javascript/mastodon/components/image.tsx | 27 ++++++++++++++++
2 files changed, 27 insertions(+), 33 deletions(-)
delete mode 100644 app/javascript/mastodon/components/image.jsx
create mode 100644 app/javascript/mastodon/components/image.tsx
diff --git a/app/javascript/mastodon/components/image.jsx b/app/javascript/mastodon/components/image.jsx
deleted file mode 100644
index 6e81ddf082..0000000000
--- a/app/javascript/mastodon/components/image.jsx
+++ /dev/null
@@ -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 (
-
- {blurhash &&
}
-

-
- );
- }
-
-}
diff --git a/app/javascript/mastodon/components/image.tsx b/app/javascript/mastodon/components/image.tsx
new file mode 100644
index 0000000000..9b4d602255
--- /dev/null
+++ b/app/javascript/mastodon/components/image.tsx
@@ -0,0 +1,27 @@
+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 = ({ src, srcSet, blurhash, className }) => {
+ const [loaded, setLoaded] = useState(false);
+
+ const handleLoad = useCallback(() => {
+ setLoaded(true);
+ }, [setLoaded]);
+
+ return (
+
+ {blurhash &&
}
+

+
+ );
+};
+
+export default Image;
From 76264e3fe86d1ac3c9f6d91290e77db8d9272d1a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E3=81=9F=E3=81=84=E3=81=A1=20=E3=81=B2?=
Date: Mon, 8 May 2023 18:12:53 +0900
Subject: [PATCH 05/13] Rewrite RadioButton component as FC (#24897)
---
.../mastodon/components/radio_button.jsx | 35 -------------------
.../mastodon/components/radio_button.tsx | 30 ++++++++++++++++
2 files changed, 30 insertions(+), 35 deletions(-)
delete mode 100644 app/javascript/mastodon/components/radio_button.jsx
create mode 100644 app/javascript/mastodon/components/radio_button.tsx
diff --git a/app/javascript/mastodon/components/radio_button.jsx b/app/javascript/mastodon/components/radio_button.jsx
deleted file mode 100644
index 0496fa2868..0000000000
--- a/app/javascript/mastodon/components/radio_button.jsx
+++ /dev/null
@@ -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 (
-
- );
- }
-
-}
diff --git a/app/javascript/mastodon/components/radio_button.tsx b/app/javascript/mastodon/components/radio_button.tsx
new file mode 100644
index 0000000000..9ba098f78d
--- /dev/null
+++ b/app/javascript/mastodon/components/radio_button.tsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import classNames from 'classnames';
+
+type Props = {
+ value: string;
+ checked: boolean;
+ name: string;
+ onChange: (event: React.ChangeEvent) => void;
+ label: React.ReactNode;
+};
+
+export const RadioButton: React.FC = ({ name, value, checked, onChange, label }) => {
+ return (
+
+ );
+};
+
+export default RadioButton;
From 7c1305b3a4ce0ee39aa923a1f1ac604794265b0b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E3=81=9F=E3=81=84=E3=81=A1=20=E3=81=B2?=
Date: Mon, 8 May 2023 18:28:36 +0900
Subject: [PATCH 06/13] Add TypeScript support for `mastodon` alias and image
imports (#24895)
---
.../mastodon/components/hashtag.jsx | 2 --
app/javascript/types/image.d.ts | 34 +++++++++++++++++++
tsconfig.json | 13 +++++--
3 files changed, 45 insertions(+), 4 deletions(-)
create mode 100644 app/javascript/types/image.d.ts
diff --git a/app/javascript/mastodon/components/hashtag.jsx b/app/javascript/mastodon/components/hashtag.jsx
index 254fae2fe0..d03b1a45a7 100644
--- a/app/javascript/mastodon/components/hashtag.jsx
+++ b/app/javascript/mastodon/components/hashtag.jsx
@@ -5,9 +5,7 @@ import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { Link } from 'react-router-dom';
-// @ts-expect-error
import ShortNumber from 'mastodon/components/short_number';
-// @ts-expect-error
import Skeleton from 'mastodon/components/skeleton';
import classNames from 'classnames';
diff --git a/app/javascript/types/image.d.ts b/app/javascript/types/image.d.ts
new file mode 100644
index 0000000000..8bd6ab0286
--- /dev/null
+++ b/app/javascript/types/image.d.ts
@@ -0,0 +1,34 @@
+declare module '*.avif' {
+ const path: string;
+ export default path;
+}
+
+declare module '*.gif' {
+ const path: string;
+ export default path;
+}
+
+declare module '*.jpg' {
+ const path: string;
+ export default path;
+}
+
+declare module '*.jpg' {
+ const path: string;
+ export default path;
+}
+
+declare module '*.png' {
+ const path: string;
+ export default path;
+}
+
+declare module '*.svg' {
+ const path: string;
+ export default path;
+}
+
+declare module '*.webp' {
+ const path: string;
+ export default path;
+}
diff --git a/tsconfig.json b/tsconfig.json
index 505b19d89b..09cea2a75f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -7,7 +7,16 @@
"noEmit": true,
"strict": true,
"esModuleInterop": true,
- "skipLibCheck": true
+ "skipLibCheck": true,
+ "baseUrl": "./",
+ "paths": {
+ "mastodon": ["app/javascript/mastodon"],
+ "mastodon/*": ["app/javascript/mastodon/*"]
+ }
},
- "include": ["app/javascript/mastodon", "app/javascript/packs"]
+ "include": [
+ "app/javascript/mastodon",
+ "app/javascript/packs",
+ "app/javascript/types"
+ ]
}
From 5bc8e2d1fdc3f1b1a0b9af5aed762d44e048250c Mon Sep 17 00:00:00 2001
From: fusagiko / takayamaki <24884114+takayamaki@users.noreply.github.com>
Date: Mon, 8 May 2023 22:10:21 +0900
Subject: [PATCH 07/13] Use LayoutType from is_mobile in actions/app (#24863)
---
app/javascript/mastodon/actions/app.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/javascript/mastodon/actions/app.ts b/app/javascript/mastodon/actions/app.ts
index 0acfbfae7a..50fd317a65 100644
--- a/app/javascript/mastodon/actions/app.ts
+++ b/app/javascript/mastodon/actions/app.ts
@@ -1,10 +1,11 @@
import { createAction } from '@reduxjs/toolkit';
+import type { LayoutType } from '../is_mobile';
export const focusApp = createAction('APP_FOCUS');
export const unfocusApp = createAction('APP_UNFOCUS');
type ChangeLayoutPayload = {
- layout: 'mobile' | 'single-column' | 'multi-column';
+ layout: LayoutType;
};
export const changeLayout =
createAction('APP_LAYOUT_CHANGE');
From 9818f342735d1765baa281aaeeab2f60b8d049fe Mon Sep 17 00:00:00 2001
From: fusagiko / takayamaki <24884114+takayamaki@users.noreply.github.com>
Date: Mon, 8 May 2023 22:12:12 +0900
Subject: [PATCH 08/13] Rewrite Domain component as function component (#24896)
---
app/javascript/mastodon/components/domain.jsx | 43 -------------------
app/javascript/mastodon/components/domain.tsx | 42 ++++++++++++++++++
.../mastodon/containers/domain_container.jsx | 2 +-
3 files changed, 43 insertions(+), 44 deletions(-)
delete mode 100644 app/javascript/mastodon/components/domain.jsx
create mode 100644 app/javascript/mastodon/components/domain.tsx
diff --git a/app/javascript/mastodon/components/domain.jsx b/app/javascript/mastodon/components/domain.jsx
deleted file mode 100644
index 85ebdbde93..0000000000
--- a/app/javascript/mastodon/components/domain.jsx
+++ /dev/null
@@ -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 (
-
- );
- }
-
-}
-
-export default injectIntl(Account);
diff --git a/app/javascript/mastodon/components/domain.tsx b/app/javascript/mastodon/components/domain.tsx
new file mode 100644
index 0000000000..6cb8f7b8ff
--- /dev/null
+++ b/app/javascript/mastodon/components/domain.tsx
@@ -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 = ({ domain, onUnblockDomain, intl }) => {
+ const handleDomainUnblock = useCallback(() => {
+ onUnblockDomain(domain);
+ }, [domain, onUnblockDomain]);
+
+ return (
+
+ );
+};
+
+export const Domain = injectIntl(_Domain);
diff --git a/app/javascript/mastodon/containers/domain_container.jsx b/app/javascript/mastodon/containers/domain_container.jsx
index 8a8ba1df12..419d5d29f5 100644
--- a/app/javascript/mastodon/containers/domain_container.jsx
+++ b/app/javascript/mastodon/containers/domain_container.jsx
@@ -2,7 +2,7 @@ import React from 'react';
import { connect } from 'react-redux';
import { blockDomain, unblockDomain } from '../actions/domain_blocks';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import Domain from '../components/domain';
+import { Domain } from '../components/domain';
import { openModal } from '../actions/modal';
const messages = defineMessages({
From 89269e4b713e3291a5c8c29b8d2e7b950b60eb35 Mon Sep 17 00:00:00 2001
From: Renaud Chaput
Date: Tue, 9 May 2023 03:07:13 +0200
Subject: [PATCH 09/13] Mark `wheel` events on scrollable list as passive
(#24914)
---
app/javascript/mastodon/components/scrollable_list.jsx | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/app/javascript/mastodon/components/scrollable_list.jsx b/app/javascript/mastodon/components/scrollable_list.jsx
index 57bc881218..3f4e4a59c6 100644
--- a/app/javascript/mastodon/components/scrollable_list.jsx
+++ b/app/javascript/mastodon/components/scrollable_list.jsx
@@ -8,6 +8,7 @@ import IntersectionObserverWrapper from '../features/ui/util/intersection_observ
import { throttle } from 'lodash';
import { List as ImmutableList } from 'immutable';
import classNames from 'classnames';
+import { supportsPassiveEvents } from 'detect-passive-events';
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen';
import LoadingIndicator from './loading_indicator';
import { connect } from 'react-redux';
@@ -236,10 +237,10 @@ class ScrollableList extends PureComponent {
attachScrollListener () {
if (this.props.bindToDocument) {
document.addEventListener('scroll', this.handleScroll);
- document.addEventListener('wheel', this.handleWheel);
+ document.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : undefined);
} else {
this.node.addEventListener('scroll', this.handleScroll);
- this.node.addEventListener('wheel', this.handleWheel);
+ this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : undefined);
}
}
From 955179fc55f2db2694ab2d1e98d5ae82af430571 Mon Sep 17 00:00:00 2001
From: Renaud Chaput
Date: Tue, 9 May 2023 03:08:47 +0200
Subject: [PATCH 10/13] Dont use CommonJS (`require`, `module.exports`)
anywhere (#24913)
---
.eslintrc.js | 17 ++++++++++
app/javascript/mastodon/common.js | 2 +-
.../features/emoji/emoji_compressed.js | 2 ++
.../features/emoji/emoji_mart_data_light.js | 8 +++--
.../features/emoji/emoji_mart_search_light.js | 2 +-
.../emoji/emoji_unicode_mapping_light.js | 15 +++++----
.../mastodon/features/emoji/emoji_utils.js | 2 +-
.../features/emoji/unicode_to_filename.js | 3 ++
.../features/emoji/unicode_to_unified_name.js | 3 ++
app/javascript/mastodon/main.jsx | 3 +-
app/javascript/mastodon/performance.js | 4 +--
.../service_worker/web_push_locales.js | 3 ++
app/javascript/packs/admin.jsx | 5 ++-
app/javascript/packs/mailer.js | 2 +-
app/javascript/packs/public.jsx | 32 +++++++++++--------
app/javascript/packs/share.jsx | 15 +++++----
package.json | 2 +-
17 files changed, 79 insertions(+), 41 deletions(-)
diff --git a/.eslintrc.js b/.eslintrc.js
index 8394d98b9c..c888671ceb 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -102,6 +102,7 @@ module.exports = {
{
vars: 'all',
args: 'after-used',
+ destructuredArrayIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
@@ -208,6 +209,9 @@ module.exports = {
],
},
],
+ 'import/no-amd': 'error',
+ 'import/no-commonjs': 'error',
+ 'import/no-import-module-exports': 'error',
'import/no-webpack-loader-syntax': 'error',
'promise/always-return': 'off',
@@ -255,6 +259,7 @@ module.exports = {
'*.config.js',
'.*rc.js',
'ide-helper.js',
+ 'config/webpack/**/*',
],
env: {
@@ -264,6 +269,10 @@ module.exports = {
parserOptions: {
sourceType: 'script',
},
+
+ rules: {
+ 'import/no-commonjs': 'off',
+ },
},
{
files: [
@@ -298,5 +307,13 @@ module.exports = {
jest: true,
},
},
+ {
+ files: [
+ 'streaming/**/*',
+ ],
+ rules: {
+ 'import/no-commonjs': 'off',
+ },
+ },
],
};
diff --git a/app/javascript/mastodon/common.js b/app/javascript/mastodon/common.js
index 8f35053036..0ec8449343 100644
--- a/app/javascript/mastodon/common.js
+++ b/app/javascript/mastodon/common.js
@@ -1,7 +1,7 @@
import Rails from '@rails/ujs';
+import 'font-awesome/css/font-awesome.css';
export function start() {
- require('font-awesome/css/font-awesome.css');
require.context('../images/', true);
try {
diff --git a/app/javascript/mastodon/features/emoji/emoji_compressed.js b/app/javascript/mastodon/features/emoji/emoji_compressed.js
index 6a402f2d4b..e1bee1655d 100644
--- a/app/javascript/mastodon/features/emoji/emoji_compressed.js
+++ b/app/javascript/mastodon/features/emoji/emoji_compressed.js
@@ -1,3 +1,5 @@
+/* eslint-disable import/no-commonjs --
+ We need to use CommonJS here due to preval */
// @preval
// http://www.unicode.org/Public/emoji/5.0/emoji-test.txt
// This file contains the compressed version of the emoji data from
diff --git a/app/javascript/mastodon/features/emoji/emoji_mart_data_light.js b/app/javascript/mastodon/features/emoji/emoji_mart_data_light.js
index 49813537d7..000aeb0de4 100644
--- a/app/javascript/mastodon/features/emoji/emoji_mart_data_light.js
+++ b/app/javascript/mastodon/features/emoji/emoji_mart_data_light.js
@@ -1,8 +1,10 @@
// 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
// emojiIndex.search functionality.
-const { unicodeToUnifiedName } = require('./unicode_to_unified_name');
-const [ shortCodesToEmojiData, skins, categories, short_names ] = require('./emoji_compressed');
+import { unicodeToUnifiedName } from './unicode_to_unified_name';
+import emojiCompressed from './emoji_compressed';
+
+const [ shortCodesToEmojiData, skins, categories, short_names ] = emojiCompressed;
const emojis = {};
@@ -33,7 +35,7 @@ Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
};
});
-module.exports = {
+export {
emojis,
skins,
categories,
diff --git a/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js b/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js
index 70694ab6dd..83e154b0b2 100644
--- a/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js
+++ b/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js
@@ -1,7 +1,7 @@
// This code is largely borrowed from:
// 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';
let originalPool = {};
diff --git a/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.js b/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.js
index 1a38fde234..30fbd9e349 100644
--- a/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.js
+++ b/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.js
@@ -2,14 +2,17 @@
// (i.e. the svg filename) and a shortCode intended to be shown
// as a "title" attribute in an HTML element (aka tooltip).
+import emojiCompressed from './emoji_compressed';
+
+import { unicodeToFilename } from './unicode_to_filename';
+
const [
shortCodesToEmojiData,
- skins, // eslint-disable-line @typescript-eslint/no-unused-vars
- categories, // eslint-disable-line @typescript-eslint/no-unused-vars
- short_names, // eslint-disable-line @typescript-eslint/no-unused-vars
+ _skins,
+ _categories,
+ _short_names,
emojisWithoutShortCodes,
-] = require('./emoji_compressed');
-const { unicodeToFilename } = require('./unicode_to_filename');
+] = emojiCompressed;
// decompress
const unicodeMapping = {};
@@ -32,4 +35,4 @@ Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
});
emojisWithoutShortCodes.forEach(emojiMapData => processEmojiMapData(emojiMapData));
-module.exports = unicodeMapping;
+export default unicodeMapping;
diff --git a/app/javascript/mastodon/features/emoji/emoji_utils.js b/app/javascript/mastodon/features/emoji/emoji_utils.js
index be793526d0..83bcc9d82f 100644
--- a/app/javascript/mastodon/features/emoji/emoji_utils.js
+++ b/app/javascript/mastodon/features/emoji/emoji_utils.js
@@ -1,7 +1,7 @@
// This code is largely borrowed from:
// 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 search = [];
diff --git a/app/javascript/mastodon/features/emoji/unicode_to_filename.js b/app/javascript/mastodon/features/emoji/unicode_to_filename.js
index c75c4cd7d0..3395c77174 100644
--- a/app/javascript/mastodon/features/emoji/unicode_to_filename.js
+++ b/app/javascript/mastodon/features/emoji/unicode_to_filename.js
@@ -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:
// https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866
exports.unicodeToFilename = (str) => {
diff --git a/app/javascript/mastodon/features/emoji/unicode_to_unified_name.js b/app/javascript/mastodon/features/emoji/unicode_to_unified_name.js
index d29550f122..108b911222 100644
--- a/app/javascript/mastodon/features/emoji/unicode_to_unified_name.js
+++ b/app/javascript/mastodon/features/emoji/unicode_to_unified_name.js
@@ -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) {
while (str.length < num) {
str = '0' + str;
diff --git a/app/javascript/mastodon/main.jsx b/app/javascript/mastodon/main.jsx
index 88a205dd24..d8654adedb 100644
--- a/app/javascript/mastodon/main.jsx
+++ b/app/javascript/mastodon/main.jsx
@@ -5,8 +5,7 @@ import Mastodon from 'mastodon/containers/mastodon';
import { store } from 'mastodon/store/configureStore';
import { me } from 'mastodon/initial_state';
import ready from 'mastodon/ready';
-
-const perf = require('mastodon/performance');
+import * as perf from 'mastodon/performance';
/**
* @returns {Promise}
diff --git a/app/javascript/mastodon/performance.js b/app/javascript/mastodon/performance.js
index 95cf962d6b..42849c82b1 100644
--- a/app/javascript/mastodon/performance.js
+++ b/app/javascript/mastodon/performance.js
@@ -2,9 +2,8 @@
// Tools for performance debugging, only enabled in development mode.
// Open up Chrome Dev Tools, then Timeline, then User Timing to see output.
// Also see config/webpack/loaders/mark.js for the webpack loader marks.
-//
-let marky;
+import * as marky from 'marky';
if (process.env.NODE_ENV === 'development') {
if (typeof performance !== 'undefined' && performance.setResourceTimingBufferSize) {
@@ -13,7 +12,6 @@ if (process.env.NODE_ENV === 'development') {
performance.setResourceTimingBufferSize(Infinity);
}
- marky = require('marky');
// allows us to easily do e.g. ReactPerf.printWasted() while debugging
//window.ReactPerf = require('react-addons-perf');
//window.ReactPerf.start();
diff --git a/app/javascript/mastodon/service_worker/web_push_locales.js b/app/javascript/mastodon/service_worker/web_push_locales.js
index 7d713cd378..3912f75c7d 100644
--- a/app/javascript/mastodon/service_worker/web_push_locales.js
+++ b/app/javascript/mastodon/service_worker/web_push_locales.js
@@ -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`) */
+
/* @preval */
const fs = require('fs');
diff --git a/app/javascript/packs/admin.jsx b/app/javascript/packs/admin.jsx
index 038e9b4347..99e903eef3 100644
--- a/app/javascript/packs/admin.jsx
+++ b/app/javascript/packs/admin.jsx
@@ -1,6 +1,8 @@
import './public-path';
import { delegate } from '@rails/ujs';
import ready from '../mastodon/ready';
+import React from 'react';
+import ReactDOM from 'react-dom';
const setAnnouncementEndsAttributes = (target) => {
const valid = target?.value && target?.validity?.valid;
@@ -223,9 +225,6 @@ ready(() => {
setAnnouncementEndsAttributes(announcementStartsAt);
}
- const React = require('react');
- const ReactDOM = require('react-dom');
-
[].forEach.call(document.querySelectorAll('[data-admin-component]'), element => {
const componentName = element.getAttribute('data-admin-component');
const { locale, ...componentProps } = JSON.parse(element.getAttribute('data-props'));
diff --git a/app/javascript/packs/mailer.js b/app/javascript/packs/mailer.js
index a4b6d54464..a2ad5e73ac 100644
--- a/app/javascript/packs/mailer.js
+++ b/app/javascript/packs/mailer.js
@@ -1,3 +1,3 @@
-require('../styles/mailer.scss');
+import '../styles/mailer.scss';
require.context('../icons');
diff --git a/app/javascript/packs/public.jsx b/app/javascript/packs/public.jsx
index 3e832c509e..21c52fc12a 100644
--- a/app/javascript/packs/public.jsx
+++ b/app/javascript/packs/public.jsx
@@ -1,13 +1,24 @@
import './public-path';
-import escapeTextContentForBrowser from 'escape-html';
import loadPolyfills from '../mastodon/load_polyfills';
-import ready from '../mastodon/ready';
import { start } from '../mastodon/common';
+
+import escapeTextContentForBrowser from 'escape-html';
+import ready from '../mastodon/ready';
import loadKeyboardExtensions from '../mastodon/load_keyboard_extensions';
import 'cocoon-js-vanilla';
import axios from 'axios';
import { throttle } from 'lodash';
import { defineMessages } from 'react-intl';
+import * as IntlMessageFormat from 'intl-messageformat';
+import { timeAgoString } from '../mastodon/components/relative_timestamp';
+import { delegate } from '@rails/ujs';
+import * as emojify from '../mastodon/features/emoji/emoji';
+import { getLocale } from '../mastodon/locales';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { createBrowserHistory } from 'history';
+
+start();
const messages = defineMessages({
usernameTaken: { id: 'username.taken', defaultMessage: 'That username is taken. Try another' },
@@ -15,8 +26,6 @@ const messages = defineMessages({
passwordDoesNotMatch: { id: 'password_confirmation.mismatching', defaultMessage: 'Password confirmation does not match' },
});
-start();
-
window.addEventListener('message', e => {
const data = e.data || {};
@@ -33,16 +42,8 @@ window.addEventListener('message', e => {
});
});
-function main() {
- const IntlMessageFormat = require('intl-messageformat').default;
- const { timeAgoString } = require('../mastodon/components/relative_timestamp');
- const { delegate } = require('@rails/ujs');
- const emojify = require('../mastodon/features/emoji/emoji').default;
- const { getLocale } = require('../mastodon/locales');
+function loaded() {
const { localeData } = getLocale();
- const React = require('react');
- const ReactDOM = require('react-dom');
- const { createBrowserHistory } = require('history');
const scrollToDetailedStatus = () => {
const history = createBrowserHistory();
@@ -341,6 +342,11 @@ function main() {
});
}
+
+function main() {
+ ready(loaded);
+}
+
loadPolyfills()
.then(main)
.then(loadKeyboardExtensions)
diff --git a/app/javascript/packs/share.jsx b/app/javascript/packs/share.jsx
index 1225d7b529..97c3c7b0e5 100644
--- a/app/javascript/packs/share.jsx
+++ b/app/javascript/packs/share.jsx
@@ -1,23 +1,26 @@
import './public-path';
import loadPolyfills from '../mastodon/load_polyfills';
import { start } from '../mastodon/common';
+import ready from '../mastodon/ready';
+import ComposeContainer from '../mastodon/containers/compose_container';
+import React from 'react';
+import ReactDOM from 'react-dom';
start();
function loaded() {
- const ComposeContainer = require('../mastodon/containers/compose_container').default;
- const React = require('react');
- const ReactDOM = require('react-dom');
const mountNode = document.getElementById('mastodon-compose');
- if (mountNode !== null) {
- const props = JSON.parse(mountNode.getAttribute('data-props'));
+ if (mountNode) {
+ const attr = mountNode.getAttribute('data-props');
+ if(!attr) return;
+
+ const props = JSON.parse(attr);
ReactDOM.render(, mountNode);
}
}
function main() {
- const ready = require('../mastodon/ready').default;
ready(loaded);
}
diff --git a/package.json b/package.json
index 199c956695..f5d7dd09f4 100644
--- a/package.json
+++ b/package.json
@@ -76,6 +76,7 @@
"jsdom": "^21.1.2",
"lodash": "^4.17.21",
"mark-loader": "^0.1.6",
+ "marky": "^1.2.5",
"mini-css-extract-plugin": "^1.6.2",
"mkdirp": "^2.1.6",
"npmlog": "^7.0.1",
@@ -192,7 +193,6 @@
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"lint-staged": "^13.2.2",
- "marky": "^1.2.5",
"prettier": "^2.8.8",
"raf": "^3.4.1",
"react-intl-translations-manager": "^5.0.3",
From d9b93bd15e508bd19896950ca5c5d356e7e402d4 Mon Sep 17 00:00:00 2001
From: Renaud Chaput
Date: Tue, 9 May 2023 03:09:11 +0200
Subject: [PATCH 11/13] Enforce React Rules of Hooks with eslint (#24911)
---
.eslintrc.js | 2 ++
app/javascript/mastodon/actions/streaming.js | 2 ++
.../compose/containers/emoji_picker_dropdown_container.js | 1 +
.../compose/containers/language_dropdown_container.js | 1 +
package.json | 1 +
yarn.lock | 5 +++++
6 files changed, 12 insertions(+)
diff --git a/.eslintrc.js b/.eslintrc.js
index c888671ceb..b4d5efd0ee 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -4,6 +4,7 @@ module.exports = {
extends: [
'eslint:recommended',
'plugin:react/recommended',
+ 'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended',
'plugin:import/recommended',
'plugin:promise/recommended',
@@ -284,6 +285,7 @@ module.exports = {
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
+ 'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js
index 41cb99e2dd..7831f261e6 100644
--- a/app/javascript/mastodon/actions/streaming.js
+++ b/app/javascript/mastodon/actions/streaming.js
@@ -52,8 +52,10 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
/**
* @param {function(Function, Function): void} fallback
*/
+
const useFallback = fallback => {
fallback(dispatch, () => {
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- this is not a react hook
pollingId = setTimeout(() => useFallback(fallback), 20000 + randomUpTo(20000));
});
};
diff --git a/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js b/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js
index 5ec937a393..9d9a59c414 100644
--- a/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js
+++ b/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js
@@ -72,6 +72,7 @@ const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({
},
onPickEmoji: emoji => {
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- this is not a react hook
dispatch(useEmoji(emoji));
if (onPickEmoji) {
diff --git a/app/javascript/mastodon/features/compose/containers/language_dropdown_container.js b/app/javascript/mastodon/features/compose/containers/language_dropdown_container.js
index 2a040a13f4..5560fe6093 100644
--- a/app/javascript/mastodon/features/compose/containers/language_dropdown_container.js
+++ b/app/javascript/mastodon/features/compose/containers/language_dropdown_container.js
@@ -26,6 +26,7 @@ const mapDispatchToProps = dispatch => ({
},
onClose (value) {
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- this is not a react hook
dispatch(useLanguage(value));
},
diff --git a/package.json b/package.json
index f5d7dd09f4..76deabc26b 100644
--- a/package.json
+++ b/package.json
@@ -189,6 +189,7 @@
"eslint-plugin-jsx-a11y": "~6.7.1",
"eslint-plugin-promise": "~6.1.1",
"eslint-plugin-react": "~7.32.2",
+ "eslint-plugin-react-hooks": "^4.6.0",
"husky": "^8.0.3",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
diff --git a/yarn.lock b/yarn.lock
index 00c6a64806..3114f26a3e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5125,6 +5125,11 @@ eslint-plugin-promise@~6.1.1:
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz#269a3e2772f62875661220631bd4dafcb4083816"
integrity sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==
+eslint-plugin-react-hooks@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3"
+ integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==
+
eslint-plugin-react@~7.32.2:
version "7.32.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz#e71f21c7c265ebce01bcbc9d0955170c55571f10"
From 64ec41d89c28f29ef4fad5353ae887705011c8d3 Mon Sep 17 00:00:00 2001
From: Renaud Chaput
Date: Tue, 9 May 2023 03:10:04 +0200
Subject: [PATCH 12/13] Make Webpack fail on failed imports (#24908)
---
config/webpack/shared.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/config/webpack/shared.js b/config/webpack/shared.js
index 78f660cfcd..037243965f 100644
--- a/config/webpack/shared.js
+++ b/config/webpack/shared.js
@@ -64,6 +64,7 @@ module.exports = {
module: {
rules: Object.keys(rules).map(key => rules[key]),
+ strictExportPresence: true,
},
plugins: [
From c8181eb0a41c4f5c1655d4e400cab071aee4182a Mon Sep 17 00:00:00 2001
From: Renaud Chaput
Date: Tue, 9 May 2023 03:11:56 +0200
Subject: [PATCH 13/13] Enforce stricter rules for Typescript files (#24910)
---
.eslintrc.js | 6 ++++++
app/javascript/mastodon/actions/markers.js | 2 +-
app/javascript/mastodon/actions/notifications.js | 2 +-
app/javascript/mastodon/actions/timelines.js | 2 +-
app/javascript/mastodon/compare_id.ts | 2 +-
.../mastodon/components/__tests__/avatar-test.jsx | 2 +-
.../components/__tests__/avatar_overlay-test.jsx | 2 +-
app/javascript/mastodon/components/account.jsx | 8 ++++----
app/javascript/mastodon/components/animated_number.tsx | 2 --
app/javascript/mastodon/components/attachment_list.jsx | 2 +-
app/javascript/mastodon/components/avatar.tsx | 2 --
.../mastodon/components/avatar_composite.jsx | 2 +-
app/javascript/mastodon/components/avatar_overlay.tsx | 2 --
app/javascript/mastodon/components/blurhash.tsx | 10 ++++++----
app/javascript/mastodon/components/check.tsx | 2 --
.../mastodon/components/column_back_button.jsx | 2 +-
.../mastodon/components/column_back_button_slim.jsx | 2 +-
app/javascript/mastodon/components/column_header.jsx | 2 +-
.../mastodon/components/dismissable_banner.jsx | 2 +-
app/javascript/mastodon/components/domain.tsx | 2 +-
app/javascript/mastodon/components/dropdown_menu.jsx | 2 +-
.../mastodon/components/edited_timestamp/index.jsx | 4 ++--
app/javascript/mastodon/components/gifv.tsx | 2 --
app/javascript/mastodon/components/icon.tsx | 2 --
app/javascript/mastodon/components/icon_button.tsx | 2 +-
app/javascript/mastodon/components/icon_with_badge.tsx | 4 +---
app/javascript/mastodon/components/image.tsx | 4 +---
app/javascript/mastodon/components/inline_account.jsx | 2 +-
app/javascript/mastodon/components/load_gap.jsx | 2 +-
app/javascript/mastodon/components/media_gallery.jsx | 4 ++--
.../mastodon/components/not_signed_in_indicator.tsx | 2 --
.../components/picture_in_picture_placeholder.jsx | 2 +-
app/javascript/mastodon/components/poll.jsx | 4 ++--
app/javascript/mastodon/components/radio_button.tsx | 2 --
.../mastodon/components/relative_timestamp.tsx | 4 +++-
app/javascript/mastodon/components/server_banner.jsx | 2 +-
app/javascript/mastodon/components/status.jsx | 8 ++++----
.../mastodon/components/status_action_bar.jsx | 2 +-
app/javascript/mastodon/components/status_content.jsx | 2 +-
app/javascript/mastodon/components/verified_badge.tsx | 2 --
app/javascript/mastodon/features/about/index.jsx | 4 ++--
.../account/components/follow_request_note.jsx | 2 +-
.../mastodon/features/account/components/header.jsx | 6 +++---
.../features/account_gallery/components/media_item.jsx | 4 ++--
.../account_timeline/components/moved_note.jsx | 2 +-
app/javascript/mastodon/features/audio/index.jsx | 4 ++--
.../compose/components/autosuggest_account.jsx | 2 +-
.../features/compose/components/compose_form.jsx | 2 +-
.../features/compose/components/navigation_bar.jsx | 4 ++--
.../features/compose/components/poll_button.jsx | 2 +-
.../mastodon/features/compose/components/poll_form.jsx | 4 ++--
.../features/compose/components/privacy_dropdown.jsx | 4 ++--
.../features/compose/components/reply_indicator.jsx | 4 ++--
.../mastodon/features/compose/components/search.jsx | 2 +-
.../features/compose/components/search_results.jsx | 2 +-
.../mastodon/features/compose/components/upload.jsx | 2 +-
.../features/compose/components/upload_button.jsx | 2 +-
.../features/compose/components/upload_progress.jsx | 2 +-
app/javascript/mastodon/features/compose/index.jsx | 2 +-
.../direct_timeline/components/conversation.jsx | 4 ++--
.../features/directory/components/account_card.jsx | 2 +-
app/javascript/mastodon/features/directory/index.jsx | 2 +-
.../mastodon/features/explore/components/story.jsx | 2 +-
app/javascript/mastodon/features/favourites/index.jsx | 2 +-
.../mastodon/features/filters/select_filter.jsx | 2 +-
.../follow_requests/components/account_authorize.jsx | 4 ++--
.../getting_started/components/announcements.jsx | 6 +++---
.../mastodon/features/hashtag_timeline/index.jsx | 2 +-
.../mastodon/features/home_timeline/index.jsx | 4 ++--
.../mastodon/features/interaction_modal/index.jsx | 2 +-
.../features/list_adder/components/account.jsx | 2 +-
.../mastodon/features/list_adder/components/list.jsx | 4 ++--
.../features/list_editor/components/account.jsx | 4 ++--
.../features/list_editor/components/edit_list_form.jsx | 2 +-
.../features/list_editor/components/search.jsx | 2 +-
.../mastodon/features/list_timeline/index.jsx | 4 ++--
.../notifications/components/clear_column_button.jsx | 2 +-
.../features/notifications/components/filter_bar.jsx | 2 +-
.../notifications/components/follow_request.jsx | 4 ++--
.../features/notifications/components/notification.jsx | 2 +-
.../components/notifications_permission_banner.jsx | 4 ++--
.../features/notifications/components/report.jsx | 4 ++--
.../mastodon/features/notifications/index.jsx | 6 +++---
.../onboarding/components/progress_indicator.jsx | 4 ++--
.../mastodon/features/onboarding/components/step.jsx | 6 +++---
app/javascript/mastodon/features/onboarding/share.jsx | 4 ++--
.../features/picture_in_picture/components/footer.jsx | 2 +-
.../features/picture_in_picture/components/header.jsx | 4 ++--
app/javascript/mastodon/features/reblogs/index.jsx | 2 +-
.../mastodon/features/report/components/option.jsx | 2 +-
.../features/report/components/status_check_box.jsx | 6 +++---
.../mastodon/features/status/components/action_bar.jsx | 2 +-
.../mastodon/features/status/components/card.jsx | 4 ++--
.../features/status/components/detailed_status.jsx | 6 +++---
app/javascript/mastodon/features/status/index.jsx | 2 +-
.../features/subscribed_languages_modal/index.jsx | 2 +-
.../mastodon/features/ui/components/actions_modal.jsx | 2 +-
.../mastodon/features/ui/components/boost_modal.jsx | 6 +++---
.../features/ui/components/bundle_modal_error.jsx | 2 +-
.../mastodon/features/ui/components/column_header.jsx | 2 +-
.../mastodon/features/ui/components/column_link.jsx | 2 +-
.../features/ui/components/compare_history_modal.jsx | 4 ++--
.../mastodon/features/ui/components/embed_modal.jsx | 2 +-
.../mastodon/features/ui/components/filter_modal.jsx | 2 +-
.../features/ui/components/focal_point_modal.jsx | 4 ++--
.../ui/components/follow_requests_column_link.jsx | 2 +-
.../mastodon/features/ui/components/header.jsx | 2 +-
.../mastodon/features/ui/components/image_modal.jsx | 2 +-
.../mastodon/features/ui/components/media_modal.jsx | 6 +++---
.../ui/components/notifications_counter_icon.js | 2 +-
.../mastodon/features/ui/components/report_modal.jsx | 2 +-
.../mastodon/features/ui/components/zoomable_image.jsx | 2 +-
app/javascript/mastodon/features/video/index.jsx | 4 ++--
app/javascript/mastodon/reducers/compose.js | 2 +-
app/javascript/mastodon/reducers/contexts.js | 2 +-
app/javascript/mastodon/reducers/conversations.js | 2 +-
app/javascript/mastodon/reducers/index.js | 4 ++--
app/javascript/mastodon/reducers/missed_updates.ts | 2 +-
app/javascript/mastodon/reducers/notifications.js | 2 +-
app/javascript/mastodon/reducers/settings.js | 2 +-
app/javascript/mastodon/reducers/timelines.js | 2 +-
app/javascript/mastodon/uuid.ts | 2 +-
app/javascript/types/image.d.ts | 1 +
123 files changed, 175 insertions(+), 186 deletions(-)
diff --git a/.eslintrc.js b/.eslintrc.js
index b4d5efd0ee..2bbe301f04 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -297,6 +297,12 @@ module.exports = {
'@typescript-eslint/no-explicit-any': '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' }],
},
},
{
diff --git a/app/javascript/mastodon/actions/markers.js b/app/javascript/mastodon/actions/markers.js
index ca246dce78..b23ecccc39 100644
--- a/app/javascript/mastodon/actions/markers.js
+++ b/app/javascript/mastodon/actions/markers.js
@@ -1,6 +1,6 @@
import api from '../api';
import { debounce } from 'lodash';
-import compareId from '../compare_id';
+import { compareId } from '../compare_id';
import { List as ImmutableList } from 'immutable';
export const MARKERS_FETCH_REQUEST = 'MARKERS_FETCH_REQUEST';
diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js
index 93588d3c0c..0c58f8d159 100644
--- a/app/javascript/mastodon/actions/notifications.js
+++ b/app/javascript/mastodon/actions/notifications.js
@@ -13,7 +13,7 @@ import { defineMessages } from 'react-intl';
import { List as ImmutableList } from 'immutable';
import { unescapeHTML } from '../utils/html';
import { usePendingItems as preferPendingItems } from 'mastodon/initial_state';
-import compareId from 'mastodon/compare_id';
+import { compareId } from 'mastodon/compare_id';
import { requestNotificationPermission } from '../utils/notifications';
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js
index 4f772a55f0..e9e3a8e240 100644
--- a/app/javascript/mastodon/actions/timelines.js
+++ b/app/javascript/mastodon/actions/timelines.js
@@ -2,7 +2,7 @@ import { importFetchedStatus, importFetchedStatuses } from './importer';
import { submitMarkers } from './markers';
import api, { getLinks } from 'mastodon/api';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
-import compareId from 'mastodon/compare_id';
+import { compareId } from 'mastodon/compare_id';
import { usePendingItems as preferPendingItems } from 'mastodon/initial_state';
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE';
diff --git a/app/javascript/mastodon/compare_id.ts b/app/javascript/mastodon/compare_id.ts
index ae4ac6f897..3ddfb76351 100644
--- a/app/javascript/mastodon/compare_id.ts
+++ b/app/javascript/mastodon/compare_id.ts
@@ -1,4 +1,4 @@
-export default function compareId (id1: string, id2: string) {
+export function compareId (id1: string, id2: string) {
if (id1 === id2) {
return 0;
}
diff --git a/app/javascript/mastodon/components/__tests__/avatar-test.jsx b/app/javascript/mastodon/components/__tests__/avatar-test.jsx
index dd3f7b7d21..5a72fc19a1 100644
--- a/app/javascript/mastodon/components/__tests__/avatar-test.jsx
+++ b/app/javascript/mastodon/components/__tests__/avatar-test.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { fromJS } from 'immutable';
-import Avatar from '../avatar';
+import { Avatar } from '../avatar';
describe('', () => {
const account = fromJS({
diff --git a/app/javascript/mastodon/components/__tests__/avatar_overlay-test.jsx b/app/javascript/mastodon/components/__tests__/avatar_overlay-test.jsx
index 44addea832..ea75dab574 100644
--- a/app/javascript/mastodon/components/__tests__/avatar_overlay-test.jsx
+++ b/app/javascript/mastodon/components/__tests__/avatar_overlay-test.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { fromJS } from 'immutable';
-import AvatarOverlay from '../avatar_overlay';
+import { AvatarOverlay } from '../avatar_overlay';
describe(' {
const account = fromJS({
diff --git a/app/javascript/mastodon/components/account.jsx b/app/javascript/mastodon/components/account.jsx
index 6a0682e9c1..b0bbea7ce4 100644
--- a/app/javascript/mastodon/components/account.jsx
+++ b/app/javascript/mastodon/components/account.jsx
@@ -1,19 +1,19 @@
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import Avatar from './avatar';
+import { Avatar } from './avatar';
import DisplayName from './display_name';
-import IconButton from './icon_button';
+import { IconButton } from './icon_button';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { me } from '../initial_state';
-import RelativeTimestamp from './relative_timestamp';
+import { RelativeTimestamp } from './relative_timestamp';
import Skeleton from 'mastodon/components/skeleton';
import { Link } from 'react-router-dom';
import { counterRenderer } from 'mastodon/components/common_counter';
import ShortNumber from 'mastodon/components/short_number';
import classNames from 'classnames';
-import VerifiedBadge from 'mastodon/components/verified_badge';
+import { VerifiedBadge } from 'mastodon/components/verified_badge';
const messages = defineMessages({
follow: { id: 'account.follow', defaultMessage: 'Follow' },
diff --git a/app/javascript/mastodon/components/animated_number.tsx b/app/javascript/mastodon/components/animated_number.tsx
index 1673ff41bb..20ffa1a4d0 100644
--- a/app/javascript/mastodon/components/animated_number.tsx
+++ b/app/javascript/mastodon/components/animated_number.tsx
@@ -54,5 +54,3 @@ export const AnimatedNumber: React.FC = ({
);
};
-
-export default AnimatedNumber;
diff --git a/app/javascript/mastodon/components/attachment_list.jsx b/app/javascript/mastodon/components/attachment_list.jsx
index 0e23889ded..3354025c4a 100644
--- a/app/javascript/mastodon/components/attachment_list.jsx
+++ b/app/javascript/mastodon/components/attachment_list.jsx
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
-import Icon from 'mastodon/components/icon';
+import { Icon } from 'mastodon/components/icon';
const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
diff --git a/app/javascript/mastodon/components/avatar.tsx b/app/javascript/mastodon/components/avatar.tsx
index e64a8af742..8be94d3f53 100644
--- a/app/javascript/mastodon/components/avatar.tsx
+++ b/app/javascript/mastodon/components/avatar.tsx
@@ -45,5 +45,3 @@ export const Avatar: React.FC = ({
);
};
-
-export default Avatar;
diff --git a/app/javascript/mastodon/components/avatar_composite.jsx b/app/javascript/mastodon/components/avatar_composite.jsx
index 220bf5b4f8..e1fae95dc0 100644
--- a/app/javascript/mastodon/components/avatar_composite.jsx
+++ b/app/javascript/mastodon/components/avatar_composite.jsx
@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { autoPlayGif } from '../initial_state';
-import Avatar from './avatar';
+import { Avatar } from './avatar';
export default class AvatarComposite extends React.PureComponent {
diff --git a/app/javascript/mastodon/components/avatar_overlay.tsx b/app/javascript/mastodon/components/avatar_overlay.tsx
index 5c65a928c5..e8dc88896f 100644
--- a/app/javascript/mastodon/components/avatar_overlay.tsx
+++ b/app/javascript/mastodon/components/avatar_overlay.tsx
@@ -47,5 +47,3 @@ export const AvatarOverlay: React.FC
= ({
);
};
-
-export default AvatarOverlay;
diff --git a/app/javascript/mastodon/components/blurhash.tsx b/app/javascript/mastodon/components/blurhash.tsx
index 6fec6e1ef7..181e2183d0 100644
--- a/app/javascript/mastodon/components/blurhash.tsx
+++ b/app/javascript/mastodon/components/blurhash.tsx
@@ -9,13 +9,13 @@ type Props = {
children?: never;
[key: string]: any;
}
-function Blurhash({
+const Blurhash: React.FC