diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index e06205db81..89e37fa0a1 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -19,6 +19,8 @@ module.exports = { tsconfigRootDir: __dirname, }, rules: { + // needs for using optional chaining + 'no-undef': 0, 'jsx-a11y/no-autofocus': 0, // disabling because typescipt uses it's own lint (see next rule) 'no-unused-vars': 0, diff --git a/frontend/.size-limit.js b/frontend/.size-limit.js index aeea1725bf..cb12d9c255 100644 --- a/frontend/.size-limit.js +++ b/frontend/.size-limit.js @@ -13,7 +13,7 @@ module.exports = [ }, { path: 'public/deleteme.js', - limit: '18 KB', + limit: '34 KB', }, { path: 'public/counter.js', diff --git a/frontend/app/@types/bem-react-helper/index.d.ts b/frontend/app/@types/bem-react-helper.d.ts similarity index 100% rename from frontend/app/@types/bem-react-helper/index.d.ts rename to frontend/app/@types/bem-react-helper.d.ts diff --git a/frontend/app/@types/preact/index.d.ts b/frontend/app/@types/preact.d.ts similarity index 99% rename from frontend/app/@types/preact/index.d.ts rename to frontend/app/@types/preact.d.ts index 4eb7357e23..787b1f0b0a 100644 --- a/frontend/app/@types/preact/index.d.ts +++ b/frontend/app/@types/preact.d.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unused-vars */ - import * as preact from 'preact/src/jsx'; declare module 'preact/src/jsx' { diff --git a/frontend/app/components/auth/__email-login-form/auth__email-login-form.tsx b/frontend/app/components/auth/__email-login-form/auth__email-login-form.tsx index e5d97e99d1..a5c7da76f5 100644 --- a/frontend/app/components/auth/__email-login-form/auth__email-login-form.tsx +++ b/frontend/app/components/auth/__email-login-form/auth__email-login-form.tsx @@ -86,7 +86,9 @@ export class EmailLoginForm extends Component { this.usernameInputRef.current.focus(); return; } - this.tokenRef.current && this.tokenRef.current.textareaRef && this.tokenRef.current.textareaRef.select(); + if (this.tokenRef.current?.textareaRef?.current) { + this.tokenRef.current.textareaRef.current.select(); + } }; onVerificationSubmit = async (e: Event) => { diff --git a/frontend/app/components/button/button.tsx b/frontend/app/components/button/button.tsx index 25883a81db..7b102f1aaa 100644 --- a/frontend/app/components/button/button.tsx +++ b/frontend/app/components/button/button.tsx @@ -14,11 +14,11 @@ interface Props extends Omit { } export const Button = forwardRef( - ({ children, theme, mods, mix, kind, type = 'button', size, ...props }) => { + ({ children, theme, mods, mix, kind, type = 'button', size, ...props }, ref) => { const className = b('button', { mods: { kind, size }, mix }, { theme, ...mods }); return ( - ); diff --git a/frontend/app/components/comment-form/__subscribe-by-email/comment-form__subscribe-by-email.tsx b/frontend/app/components/comment-form/__subscribe-by-email/comment-form__subscribe-by-email.tsx index d136d6304e..916a70508f 100644 --- a/frontend/app/components/comment-form/__subscribe-by-email/comment-form__subscribe-by-email.tsx +++ b/frontend/app/components/comment-form/__subscribe-by-email/comment-form__subscribe-by-email.tsx @@ -1,6 +1,6 @@ /** @jsx createElement */ import { createElement, FunctionComponent, Fragment } from 'preact'; -import { useState, useCallback, useEffect, useRef } from 'preact/hooks'; +import { useState, useCallback, useEffect, useRef, PropRef } from 'preact/hooks'; import { useSelector, useDispatch } from 'react-redux'; import b from 'bem-react-helper'; @@ -80,7 +80,7 @@ const renderEmailPart = ( intl: IntlShape, emailAddress: string, handleChangeEmail: (e: Event) => void, - emailAddressRef: ReturnType + emailAddressRef: PropRef ) => (
diff --git a/frontend/app/components/comment-form/comment-form.tsx b/frontend/app/components/comment-form/comment-form.tsx index 21334c5e66..a366d56364 100644 --- a/frontend/app/components/comment-form/comment-form.tsx +++ b/frontend/app/components/comment-form/comment-form.tsx @@ -327,6 +327,7 @@ export class CommentForm extends Component { buttonText: intl.formatMessage(messages.uploading), }); + // TODO: remove legacy code, now we don't support IE // fallback for ie < 9 if (!isSelectionSupported) { for (let i = 0; i < files.length; i++) { diff --git a/frontend/app/components/comment-form/textarea-autosize.tsx b/frontend/app/components/comment-form/textarea-autosize.tsx index ff1a06ec3b..17449e2660 100644 --- a/frontend/app/components/comment-form/textarea-autosize.tsx +++ b/frontend/app/components/comment-form/textarea-autosize.tsx @@ -1,18 +1,14 @@ /** @jsx createElement */ -import { createElement, JSX, Component } from 'preact'; +import { createElement, JSX, Component, createRef, RefObject } from 'preact'; -type Props = JSX.HTMLAttributes & { +export interface Props extends Omit { autofocus: boolean; -}; + ref?: RefObject; +} +// TODO: rewrite it to functional component and add ref forwarding export default class TextareaAutosize extends Component { - textareaRef?: HTMLTextAreaElement; - - constructor(props: Props) { - super(props); - - this.onRef = this.onRef.bind(this); - } + textareaRef = createRef(); componentDidMount() { this.autoResize(); @@ -28,46 +24,67 @@ export default class TextareaAutosize extends Component { focus(): void { setTimeout(() => { - if (this.textareaRef) { - this.textareaRef.focus(); - this.textareaRef.selectionStart = this.textareaRef.selectionEnd = this.textareaRef.value.length; + const { current: textarea } = this.textareaRef; + + if (textarea) { + textarea.focus(); + textarea.selectionStart = textarea.value.length; + textarea.selectionEnd = textarea.value.length; } }, 100); } /** returns whether selectionStart api supported */ - isSelectionSupported(): boolean { - if (!this.textareaRef) throw new Error('No textarea element reference exists'); - return 'selectionStart' in this.textareaRef; + isSelectionSupported() { + const { current: textarea } = this.textareaRef; + + if (textarea) { + return 'selectionStart' in textarea; + } + + throw new Error('No textarea element reference exists'); } /** returns selection range of a textarea */ getSelection(): [number, number] { - if (!this.textareaRef) throw new Error('No textarea element reference exists'); + const { current: textarea } = this.textareaRef; + + if (textarea) { + return [textarea.selectionStart, textarea.selectionEnd]; + } - return [this.textareaRef.selectionStart, this.textareaRef.selectionEnd]; + throw new Error('No textarea element reference exists'); } /** sets selection range of a textarea */ setSelection(selection: [number, number]) { - if (!this.textareaRef) throw new Error('No textarea element reference exists'); - this.textareaRef.selectionStart = selection[0]; - this.textareaRef.selectionEnd = selection[1]; - } + const { current: textarea } = this.textareaRef; - onRef(node: HTMLTextAreaElement) { - this.textareaRef = node; + if (textarea) { + textarea.selectionStart = selection[0]; + textarea.selectionEnd = selection[1]; + return; + } + + throw new Error('No textarea element reference exists'); } + getValue() { - return this.textareaRef ? this.textareaRef.value : ''; + const { current: textarea } = this.textareaRef; + + return textarea ? textarea.value : ''; } + autoResize() { - if (this.textareaRef) { - this.textareaRef.style.height = ''; - this.textareaRef.style.height = `${this.textareaRef.scrollHeight}px`; + const { current: textarea } = this.textareaRef; + + if (textarea) { + textarea.style.height = ''; + textarea.style.height = `${textarea.scrollHeight}px`; } } + render(props: Props) { - return