use named imports

This commit is contained in:
Pavel Mineev
2021-05-06 15:55:46 -05:00
committed by Umputun
parent 123cd53c09
commit d8058c6576
69 changed files with 168 additions and 203 deletions
+1 -3
View File
@@ -2,6 +2,4 @@ import createMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockStore = createMockStore<any, any>([thunk]);
export default mockStore;
export const mockStore = createMockStore<any, any>([thunk]);
+1 -1
View File
@@ -1,6 +1,6 @@
import { Comment } from './types';
import { apiFetcher } from './fetcher';
export default function getLastComments(siteId: string, max: number): Promise<Comment[]> {
export function getLastComments(siteId: string, max: number): Promise<Comment[]> {
return apiFetcher.get(`/last/${max}`, { site: siteId });
}
+1 -1
View File
@@ -1,5 +1,5 @@
// based on https://github.com/sindresorhus/copy-text-to-clipboard, but improved to copy text styles too
export default function copy(input: string): boolean {
export function copy(input: string): boolean {
const element = document.createElement('textarea') as HTMLTextAreaElement;
const previouslyFocusedElement = document.activeElement as HTMLElement;
+1 -1
View File
@@ -1,4 +1,4 @@
import parseQuery from 'utils/parseQuery';
import { parseQuery } from 'utils/parseQuery';
import type { Theme } from './types';
import { THEMES, MAX_SHOWN_ROOT_COMMENTS } from './constants';
+1 -1
View File
@@ -1,4 +1,4 @@
import parseQuery from 'utils/parseQuery';
import { parseQuery } from 'utils/parseQuery';
import type { UserInfo } from './types';
@@ -8,7 +8,7 @@ import { IntlProvider } from 'react-intl';
import type { User } from 'common/types';
import enMessages from 'locales/en.json';
import AuthPanel, { Props } from './auth-panel';
import { AuthPanel, Props } from './auth-panel';
const DefaultProps = {
postInfo: {
@@ -6,16 +6,15 @@ import b from 'bem-react-helper';
import { User, Sorting, Theme, PostInfo } from 'common/types';
import { IS_STORAGE_AVAILABLE, IS_THIRD_PARTY } from 'common/constants';
import { requestDeletion } from 'utils/email';
import postMessage from 'utils/postMessage';
import { postMessage } from 'utils/postMessage';
import { getHandleClickProps } from 'common/accessibility';
import { StoreState } from 'store';
import { Dropdown, DropdownItem } from 'components/dropdown';
import { Button } from 'components/button';
import Auth from 'components/auth';
import { Auth } from 'components/auth';
import { useTheme } from 'hooks/useTheme';
import useTheme from 'hooks/useTheme';
export interface OwnProps {
interface OwnProps {
user: User | null;
hiddenUsers: StoreState['hiddenUsers'];
isCommentsDisabled: boolean;
@@ -40,7 +39,7 @@ interface State {
sortSelectFocused: boolean;
}
export class AuthPanel extends Component<Props, State> {
class AuthPanelComponent extends Component<Props, State> {
state = {
isBlockedVisible: false,
anonymousUsernameInputValue: 'anon',
@@ -319,10 +318,10 @@ function getSortArray(currentSort: Sorting, intl: IntlShape) {
});
}
export default function AuthPanelConnected(props: OwnProps) {
export function AuthPanel(props: OwnProps) {
const intl = useIntl();
const theme = useTheme();
const sort = useSelector<StoreState, Sorting>((state) => state.comments.sort);
return <AuthPanel intl={intl} theme={theme} sort={sort} {...props} />;
return <AuthPanelComponent intl={intl} theme={theme} sort={sort} {...props} />;
}
+1 -1
View File
@@ -15,4 +15,4 @@ import './_theme/_light/auth-panel_theme_light.css';
import './_logged-in/auth-panel_logged-in.css';
export { default } from './auth-panel';
export * from './auth-panel';
@@ -1,6 +1,6 @@
import { defineMessages } from 'react-intl';
const messages = defineMessages({
export const messages = defineMessages({
signin: {
id: 'auth.signin',
defaultMessage: 'Sign In',
@@ -58,5 +58,3 @@ const messages = defineMessages({
defaultMessage: 'Submit',
},
});
export default messages;
+4 -2
View File
@@ -5,7 +5,7 @@ import { fireEvent, render, waitFor } from '@testing-library/preact';
import { OAuthProvider, User } from 'common/types';
import { StaticStore } from 'common/static-store';
import Auth from './auth';
import { Auth } from './auth';
import * as utils from './auth.utils';
import * as api from './auth.api';
import { getProviderData } from './components/oauth.utils';
@@ -14,7 +14,9 @@ jest.mock('react-redux', () => ({
useDispatch: () => jest.fn(),
}));
jest.mock('hooks/useTheme', () => () => 'light');
jest.mock('hooks/useTheme', () => ({
useTheme: () => 'light',
}));
describe('<Auth/>', () => {
let defaultProviders = StaticStore.config.auth_providers;
+6 -6
View File
@@ -6,18 +6,18 @@ import { useDispatch } from 'react-redux';
import { setUser } from 'store/user/actions';
import { Input } from 'components/input';
import TextareaAutosize from 'components/textarea-autosize';
import { TextareaAutosize } from 'components/textarea-autosize';
import Button from './components/button';
import OAuthProviders from './components/oauth';
import messages from './auth.messsages';
import { Button } from './components/button';
import { OAuth } from './components/oauth';
import { messages } from './auth.messsages';
import { useDropdown } from './auth.hooks';
import { getProviders, getTokenInvalidReason } from './auth.utils';
import { emailSignin, verifyEmailSignin, anonymousSignin } from './auth.api';
import styles from './auth.module.css';
export default function Auth() {
export function Auth() {
const intl = useIntl();
const dispath = useDispatch();
const [oauthProviders, formProviders] = getProviders();
@@ -199,7 +199,7 @@ export default function Auth() {
<h5 className={clsx('auth-form-title', styles.title)}>
{intl.formatMessage(messages.oauthSource)}
</h5>
<OAuthProviders providers={oauthProviders} />
<OAuth providers={oauthProviders} />
</>
)}
{hasOAuthProviders && hasFormProviders && (
+1 -1
View File
@@ -3,7 +3,7 @@ import { StaticStore } from 'common/static-store';
import type { FormProvider, OAuthProvider } from 'common/types';
import { OAUTH_PROVIDERS } from './components/oauth.consts';
import messages from './auth.messsages';
import { messages } from './auth.messsages';
export function getProviders(): [OAuthProvider[], FormProvider[]] {
const oauthProviders: OAuthProvider[] = [];
@@ -11,7 +11,7 @@ type Props = Omit<JSX.HTMLAttributes<HTMLButtonElement>, 'size'> & {
selected?: boolean;
};
export default function Button({ children, size, kind, suffix, selected, className, ...props }: Props) {
export function Button({ children, size, kind, suffix, selected, className, ...props }: Props) {
return (
<button
className={clsx(className, styles.button, kind && styles[kind], size && styles[size], {
@@ -5,14 +5,16 @@ import type { User } from 'common/types';
import * as userActions from 'store/user/actions';
import { BASE_URL } from 'common/constants.config';
import OAuth from './oauth';
import { OAuth } from './oauth';
import * as api from './oauth.api';
jest.mock('react-redux', () => ({
useDispatch: () => jest.fn(),
}));
jest.mock('hooks/useTheme', () => () => 'light');
jest.mock('hooks/useTheme', () => ({
useTheme: () => 'light',
}));
describe('<OAuth />', () => {
it('should have permanent class name', () => {
@@ -3,17 +3,17 @@ import { useDispatch } from 'react-redux';
import clsx from 'clsx';
import { useIntl } from 'react-intl';
import { siteId } from 'common/settings';
import type { OAuthProvider } from 'common/types';
import useTheme from 'hooks/useTheme';
import { siteId } from 'common/settings';
import { useTheme } from 'hooks/useTheme';
import { setUser } from 'store/user/actions';
import messages from 'components/auth/auth.messsages';
import { messages } from 'components/auth/auth.messsages';
import { getButtonVariant, getProviderData } from './oauth.utils';
import { oauthSignin } from './oauth.api';
import styles from './oauth.module.css';
import { BASE_URL } from 'common/constants.config';
import { getButtonVariant, getProviderData } from './oauth.utils';
import styles from './oauth.module.css';
const location = encodeURIComponent(`${window.location.origin}${window.location.pathname}?selfClose`);
@@ -21,7 +21,7 @@ type Props = {
providers: OAuthProvider[];
};
export default function OAuthProviders({ providers }: Props) {
export function OAuth({ providers }: Props) {
const intl = useIntl();
const dispath = useDispatch();
const theme = useTheme();
@@ -1,5 +1,5 @@
import { OAuthProvider, Theme } from 'common/types';
import capitalizeFirstLetter from 'utils/capitalize-first-letter';
import { capitalizeFirstLetter } from 'utils/capitalize-first-letter';
import { OAUTH_DATA } from './oauth.consts';
+1 -1
View File
@@ -1 +1 @@
export { default } from './auth';
export * from './auth';
@@ -10,14 +10,14 @@ import { StoreState } from 'store';
import { setUserSubscribed } from 'store/user/actions';
import { sleep } from 'utils/sleep';
import { extractErrorMessageFromResponse } from 'utils/errorUtils';
import useTheme from 'hooks/useTheme';
import { useTheme } from 'hooks/useTheme';
import { getHandleClickProps } from 'common/accessibility';
import { emailVerificationForSubscribe, emailConfirmationForSubscribe, unsubscribeFromEmailUpdates } from 'common/api';
import { Input } from 'components/input';
import { Button } from 'components/button';
import { Dropdown } from 'components/dropdown';
import Preloader from 'components/preloader';
import TextareaAutosize from 'components/textarea-autosize';
import { Preloader } from 'components/preloader';
import { TextareaAutosize } from 'components/textarea-autosize';
import { isUserAnonymous } from 'utils/isUserAnonymous';
import { isJwtExpired } from 'utils/jwt';
@@ -2,7 +2,7 @@ import { h, FunctionComponent } from 'preact';
import { useMemo } from 'preact/hooks';
import { useIntl, defineMessages } from 'react-intl';
import useTheme from 'hooks/useTheme';
import { useTheme } from 'hooks/useTheme';
import { siteId, url } from 'common/settings';
import { BASE_URL, API_BASE } from 'common/constants';
import { Dropdown, DropdownItem } from 'components/dropdown';
@@ -5,7 +5,7 @@ import { user, anonymousUser } from '__stubs__/user';
import { StaticStore } from 'common/static-store';
import { LS_SAVED_COMMENT_VALUE } from 'common/constants';
import * as localStorageModule from 'common/local-storage';
import TextareaAutosize from 'components/textarea-autosize';
import { TextareaAutosize } from 'components/textarea-autosize';
import { CommentForm, CommentFormProps, messages } from './comment-form';
import { SubscribeByEmail } from './__subscribe-by-email';
@@ -10,15 +10,15 @@ import { isUserAnonymous } from 'utils/isUserAnonymous';
import { sleep } from 'utils/sleep';
import { replaceSelection } from 'utils/replaceSelection';
import { Button } from 'components/button';
import TextareaAutosize from 'components/textarea-autosize';
import Auth from 'components/auth';
import { TextareaAutosize } from 'components/textarea-autosize';
import { Auth } from 'components/auth';
import { getJsonItem, updateJsonItem } from 'common/local-storage';
import { LS_SAVED_COMMENT_VALUE } from 'common/constants';
import { SubscribeByEmail } from './__subscribe-by-email';
import { SubscribeByRSS } from './__subscribe-by-rss';
import MarkdownToolbar from './markdown-toolbar';
import { MarkdownToolbar } from './markdown-toolbar';
import { TextExpander } from './text-expander';
let textareaId = 0;
@@ -1,6 +1,6 @@
import { h } from 'preact';
export default function BoldIcon() {
export function BoldIcon() {
return (
<svg
className="comment-form__toolbar-icon"
@@ -1,6 +1,6 @@
import { h } from 'preact';
export default function CodeIcon() {
export function CodeIcon() {
return (
<svg
className="comment-form__toolbar-icon"
@@ -1,6 +1,6 @@
import { h } from 'preact';
export default function HeaderIcon() {
export function HeaderIcon() {
return (
<svg
className="comment-form__toolbar-icon"
@@ -1,6 +1,6 @@
import { h } from 'preact';
export default function ImageIcon() {
export function ImageIcon() {
return (
<svg className="comment-form__toolbar-icon" width="11.25" height="15" viewBox="0 0 384 512" aria-hidden="true">
<path
@@ -1,6 +1,6 @@
import { h } from 'preact';
export default function ItalicIcon() {
export function ItalicIcon() {
return (
<svg
className="comment-form__toolbar-icon"
@@ -1,6 +1,6 @@
import { h } from 'preact';
export default function LinkIcon() {
export function LinkIcon() {
return (
<svg
className="comment-form__toolbar-icon"
@@ -1,6 +1,6 @@
import { h } from 'preact';
export default function OrderedListIcon() {
export function OrderedListIcon() {
return (
<svg
className="comment-form__toolbar-icon"
@@ -1,6 +1,6 @@
import { h } from 'preact';
export default function QuoteIcon() {
export function QuoteIcon() {
return (
<svg
className="comment-form__toolbar-icon"
@@ -1,6 +1,6 @@
import { h } from 'preact';
export default function UnorderedListIcon() {
export function UnorderedListIcon() {
return (
<svg
className="comment-form__toolbar-icon"
@@ -4,15 +4,15 @@ import { h, Component } from 'preact';
import { defineMessages, IntlShape } from 'react-intl';
// TODO: Use SVGR
import BoldIcon from './markdown-toolbar-icons/bold-icon';
import HeaderIcon from './markdown-toolbar-icons/header-icon';
import ItalicIcon from './markdown-toolbar-icons/italic-icon';
import QuoteIcon from './markdown-toolbar-icons/quote-icon';
import CodeIcon from './markdown-toolbar-icons/code-icon';
import LinkIcon from './markdown-toolbar-icons/link-icon';
import ImageIcon from './markdown-toolbar-icons/image-icon';
import UnorderedListIcon from './markdown-toolbar-icons/unordered-list-icon';
import OrderedListIcon from './markdown-toolbar-icons/ordered-list-icon';
import { BoldIcon } from './markdown-toolbar-icons/bold-icon';
import { HeaderIcon } from './markdown-toolbar-icons/header-icon';
import { ItalicIcon } from './markdown-toolbar-icons/italic-icon';
import { QuoteIcon } from './markdown-toolbar-icons/quote-icon';
import { CodeIcon } from './markdown-toolbar-icons/code-icon';
import { LinkIcon } from './markdown-toolbar-icons/link-icon';
import { ImageIcon } from './markdown-toolbar-icons/image-icon';
import { UnorderedListIcon } from './markdown-toolbar-icons/unordered-list-icon';
import { OrderedListIcon } from './markdown-toolbar-icons/ordered-list-icon';
interface Props {
intl: IntlShape;
@@ -69,7 +69,7 @@ const messages = defineMessages({
},
});
export default class MarkdownToolbar extends Component<Props> {
export class MarkdownToolbar extends Component<Props> {
constructor(props: Props) {
super(props);
this.uploadImages = this.uploadImages.bind(this);
@@ -5,7 +5,7 @@ import clsx from 'clsx';
import { StaticStore } from 'common/static-store';
import { Theme } from 'common/types';
import useTheme from 'hooks/useTheme';
import { useTheme } from 'hooks/useTheme';
import styles from './text-expander.module.css';
@@ -7,7 +7,7 @@ import type { User, Comment as CommentType, PostInfo } from 'common/types';
import { StaticStore } from 'common/static-store';
import { sleep } from 'utils/sleep';
import Comment, { CommentProps } from './comment';
import { Comment, CommentProps } from './comment';
const mount = <T extends JSX.Element>(component: T) =>
enzymeMount(
+5 -7
View File
@@ -5,8 +5,8 @@ import { getHandleClickProps } from 'common/accessibility';
import { API_BASE, BASE_URL, COMMENT_NODE_CLASSNAME_PREFIX } from 'common/constants';
import { StaticStore } from 'common/static-store';
import debounce from 'utils/debounce';
import copy from 'common/copy';
import { debounce } from 'utils/debounce';
import { copy } from 'common/copy';
import { Theme, BlockTTL, Comment as CommentType, PostInfo, User, CommentMode } from 'common/types';
import { extractErrorMessageFromResponse, FetcherError } from 'utils/errorUtils';
import { isUserAnonymous } from 'utils/isUserAnonymous';
@@ -14,9 +14,9 @@ import { isUserAnonymous } from 'utils/isUserAnonymous';
import { CommentFormProps } from 'components/comment-form';
import { AvatarIcon } from 'components/avatar-icon';
import { Button } from 'components/button';
import Countdown from 'components/countdown';
import { Countdown } from 'components/countdown';
import { getPreview, uploadImage } from 'common/api';
import postMessage from 'utils/postMessage';
import { postMessage } from 'utils/postMessage';
import { FormattedMessage, useIntl, IntlShape, defineMessages } from 'react-intl';
import { getVoteMessage, VoteMessagesTypes } from './getVoteMessage';
import { getBlockingDurations } from './getBlockingDurations';
@@ -137,7 +137,7 @@ export interface State {
initial: boolean;
}
class Comment extends Component<CommentProps, State> {
export class Comment extends Component<CommentProps, State> {
votingPromise: Promise<unknown> = Promise.resolve();
/** comment text node. Used in comment text copying */
textNode = createRef<HTMLDivElement>();
@@ -898,5 +898,3 @@ function FormatTime({ time }: { time: Date }) {
/>
);
}
export default Comment;
@@ -15,7 +15,7 @@ import { StoreState } from 'store';
import { addComment, removeComment, updateComment, setPinState, putVote, setCommentMode } from 'store/comments/actions';
import { blockUser, unblockUser, hideUser, setVerifiedStatus } from 'store/user/actions';
import Comment, { CommentProps } from './comment';
import { Comment, CommentProps } from './comment';
import { getCommentMode } from 'store/comments/getters';
import { uploadImage, getPreview } from 'common/api';
import { getThreadIsCollapsed } from 'store/thread/getters';
+1 -1
View File
@@ -1 +1 @@
export { default } from './comment';
export * from './comment';
+1 -1
View File
@@ -12,7 +12,7 @@ interface State {
}
/** Component which uses plain DOM mutation instead of rerendering react reactive reactivity */
export default class Countdown extends Component<Props, State> {
export class Countdown extends Component<Props, State> {
elemRef = createRef<HTMLSpanElement>();
intervalID?: number;
constructor(props: Props) {
@@ -1,3 +1,3 @@
import './__item/list-comments__item.css';
export { default } from './list-comments';
export * from './list-comments';
@@ -1,17 +1,17 @@
import { h, FunctionComponent } from 'preact';
import { h } from 'preact';
import { useIntl } from 'react-intl';
import clsx from 'clsx';
import type { Comment as CommentType } from 'common/types';
import Comment from 'components/comment';
import { Comment } from 'components/comment';
import styles from './list-comments.module.css';
export type ListCommentsProps = {
type Props = {
comments: CommentType[];
};
const ListComments: FunctionComponent<ListCommentsProps> = ({ comments = [] }) => {
export function ListComments({ comments = [] }: Props) {
const intl = useIntl();
return (
@@ -33,6 +33,4 @@ const ListComments: FunctionComponent<ListCommentsProps> = ({ comments = [] }) =
))}
</div>
);
};
export default ListComments;
}
+1 -1
View File
@@ -1,3 +1,3 @@
export { default } from './preloader';
export * from './preloader';
// all styles were moved to iframe.html
@@ -1,10 +1,10 @@
import { h, FunctionComponent } from 'preact';
import { h } from 'preact';
import b, { Mix } from 'bem-react-helper';
interface Props {
type Props = {
mix?: Mix;
};
export function Preloader({ mix }: Props) {
return <div className={b('preloader', { mix })} />;
}
const Preloader: FunctionComponent<Props> = ({ mix }) => <div className={b('preloader', { mix })} />;
export default Preloader;
@@ -24,11 +24,11 @@ function getObserver(): { observer: IntersectionObserver; instanceMap: WeakMap<E
return { observer, instanceMap };
}
type InViewProps = {
type Props = {
children: <T>(props: { inView: boolean; ref: PropRef<T> }) => VNode;
};
const InView = ({ children }: InViewProps) => {
export function InView({ children }: Props) {
const [inView, setInView] = useState(false);
const ref = useRef<Component<unknown, unknown>>(null);
@@ -53,6 +53,4 @@ const InView = ({ children }: InViewProps) => {
}, []);
return children({ inView, ref });
};
export default InView;
}
+4 -4
View File
@@ -30,16 +30,16 @@ import { setCommentsReadOnlyState } from 'store/post-info/actions';
import { setTheme } from 'store/theme/actions';
import { Button } from 'components/button';
import Preloader from 'components/preloader';
import Settings from 'components/settings';
import AuthPanel from 'components/auth-panel';
import { Preloader } from 'components/preloader';
import { Settings } from 'components/settings';
import { AuthPanel } from 'components/auth-panel';
import { CommentForm } from 'components/comment-form';
import { Thread } from 'components/thread';
import { ConnectedComment as Comment } from 'components/comment/connected-comment';
import { uploadImage, getPreview } from 'common/api';
import { isUserAnonymous } from 'utils/isUserAnonymous';
import { bindActions } from 'utils/actionBinder';
import postMessage from 'utils/postMessage';
import { postMessage } from 'utils/postMessage';
import { useActions } from 'hooks/useAction';
import { setCollapse } from 'store/thread/actions';
import { logout } from 'components/auth/auth.api';
+1 -4
View File
@@ -1,6 +1,3 @@
import withTheme from '../with-theme';
import Settings from './settings';
import './settings.css';
import './__action/settings__action.css';
@@ -13,4 +10,4 @@ import './__user-id/settings__user-id.css';
import './_theme/_dark/settings_theme_dark.css';
import './_theme/_light/settings_theme_light.css';
export default withTheme(Settings);
export * from './settings';
@@ -5,6 +5,7 @@ import { User, BlockedUser, Theme, BlockTTL } from 'common/types';
import { getHandleClickProps } from 'common/accessibility';
import { StoreState } from 'store';
import { defineMessages, IntlShape, FormattedMessage, useIntl } from 'react-intl';
import { useTheme } from 'hooks/useTheme';
interface Props {
theme: Theme;
@@ -49,7 +50,7 @@ const messages = defineMessages({
},
});
export default class Settings extends Component<Props, State> {
class SettingsComponent extends Component<Props, State> {
constructor(props: Props) {
super(props);
@@ -205,10 +206,15 @@ export default class Settings extends Component<Props, State> {
}
}
const currentYear = new Date().getFullYear();
export function Settings(props: Omit<Props, 'theme'>) {
const theme = useTheme();
return <SettingsComponent theme={theme} {...props} />;
}
function FormatTime({ time }: { time: Date }) {
const intl = useIntl();
const currentYear = new Date().getFullYear();
// let's assume that if block ttl is more than 50 years then user blocked permanently
if (time.getFullYear() - currentYear >= 50)
return <FormattedMessage id="settings.permanently" defaultMessage="permanently" />;
+15 -19
View File
@@ -2,32 +2,28 @@ import { h, JSX } from 'preact';
import { forwardRef } from 'preact/compat';
import { useEffect, useRef } from 'preact/hooks';
export type TextareaAutosizeProps = JSX.HTMLAttributes<HTMLTextAreaElement> & {};
function autoResize(textarea: HTMLTextAreaElement) {
textarea.style.height = '';
textarea.style.height = `${textarea.scrollHeight}px`;
}
const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
({ onInput, value, ...props }, externalRef) => {
const localRef = useRef<HTMLTextAreaElement>();
const ref = externalRef || localRef;
type Props = JSX.HTMLAttributes<HTMLTextAreaElement>;
const handleInput: JSX.GenericEventHandler<HTMLTextAreaElement> = (evt) => {
if (onInput) {
return onInput.call(ref.current, evt);
}
export const TextareaAutosize = forwardRef<HTMLTextAreaElement, Props>(({ onInput, value, ...props }, externalRef) => {
const localRef = useRef<HTMLTextAreaElement>();
const ref = externalRef || localRef;
autoResize(ref.current);
};
const handleInput: JSX.GenericEventHandler<HTMLTextAreaElement> = (evt) => {
if (onInput) {
return onInput.call(ref.current, evt);
}
useEffect(() => {
autoResize(ref.current);
}, [value, ref]);
autoResize(ref.current);
};
return <textarea {...props} onInput={handleInput} value={value} ref={ref} />;
}
);
useEffect(() => {
autoResize(ref.current);
}, [value, ref]);
export default TextareaAutosize;
return <textarea {...props} onInput={handleInput} value={value} ref={ref} />;
});
+1 -1
View File
@@ -9,7 +9,7 @@ import { getHandleClickProps } from 'common/accessibility';
import { StoreState } from 'store';
import { setCollapse } from 'store/thread/actions';
import { getThreadIsCollapsed } from 'store/thread/getters';
import InView from 'components/root/in-view/in-view';
import { InView } from 'components/root/in-view/in-view';
import { ConnectedComment as Comment } from 'components/comment/connected-comment';
import { CommentForm } from 'components/comment-form';
@@ -1,12 +1,17 @@
import { h, Fragment } from 'preact';
import { useIntl } from 'react-intl';
import { Comment as CommentType } from 'common/types';
import Comment from 'components/comment';
import Preloader from 'components/preloader';
import { useIntl } from 'react-intl';
import { Comment } from 'components/comment';
import { Preloader } from 'components/preloader';
const LastCommentsList = ({ comments, isLoading }: { comments: CommentType[]; isLoading: boolean }) => {
type Props = {
comments: CommentType[];
isLoading: boolean;
};
export function LastCommentsList({ comments, isLoading }: Props) {
const intl = useIntl();
if (isLoading) {
@@ -31,6 +36,4 @@ const LastCommentsList = ({ comments, isLoading }: { comments: CommentType[]; is
))}
</>
);
};
export default LastCommentsList;
}
@@ -8,12 +8,12 @@ import { Comment } from 'common/types';
import { fetchInfo } from 'store/user-info/actions';
import { userInfo } from 'common/user-info-settings';
import postMessage from 'utils/postMessage';
import { postMessage } from 'utils/postMessage';
import { bindActions } from 'utils/actionBinder';
import { useActions } from 'hooks/useAction';
import { AvatarIcon } from '../avatar-icon';
import LastCommentsList from './last-comments-list';
import { LastCommentsList } from './last-comments-list';
const boundActions = bindActions({ fetchInfo });
@@ -1,22 +0,0 @@
import { h, FunctionComponent, ComponentType } from 'preact';
import { useSelector } from 'react-redux';
import { StoreState } from 'store';
import { Theme } from 'common/types';
const themeSelector = (state: StoreState) => state.theme;
/**
* Connects redux theme property to component's
*/
function withTheme<P extends { theme: Theme }>(PlainComponent: ComponentType<P>) {
const C: FunctionComponent<Omit<P, 'theme'>> = (props) => {
const theme = useSelector(themeSelector);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return <PlainComponent theme={theme} {...(props as any)} />;
};
C.displayName = `withTheme(${PlainComponent.displayName || PlainComponent.name})`;
return C;
}
export default withTheme;
+1 -1
View File
@@ -3,7 +3,7 @@ import { useSelector } from 'react-redux';
import { StoreState } from 'store';
import { Theme } from 'common/types';
export default function useTheme() {
export function useTheme() {
const theme = useSelector<StoreState, Theme>(({ theme }) => theme);
return theme;
+2 -2
View File
@@ -1,11 +1,11 @@
import { h, render } from 'preact';
import { IntlProvider } from 'react-intl';
import getLastComments from 'common/api.getLastComments';
import { getLastComments } from 'common/api.getLastComments';
import { BASE_URL } from 'common/constants.config';
import { loadLocale } from 'utils/loadLocale';
import { getLocale } from 'utils/getLocale';
import ListComments from 'components/list-comments';
import { ListComments } from 'components/list-comments';
const LAST_COMMENTS_NODE_CLASSNAME = 'remark42__last-comments';
const DEFAULT_LAST_COMMENTS_MAX = 15;
+4 -4
View File
@@ -7,13 +7,13 @@ import { loadLocale } from 'utils/loadLocale';
import { getLocale } from 'utils/getLocale';
import { ConnectedRoot } from 'components/root';
import { UserInfo } from 'components/user-info';
import reduxStore from 'store';
import { store } from 'store';
import { NODE_ID, BASE_URL } from 'common/constants';
import { StaticStore } from 'common/static-store';
import { getConfig } from 'common/api';
import { fetchHiddenUsers } from 'store/user/actions';
import { restoreCollapsedThreads } from 'store/thread/actions';
import parseQuery from 'utils/parseQuery';
import { parseQuery } from 'utils/parseQuery';
import { parseBooleansFromDictionary } from 'utils/parse-booleans-from-dictionary';
if (document.readyState === 'loading') {
@@ -34,7 +34,7 @@ async function init(): Promise<void> {
const params = parseQuery<{ page?: string; locale?: string; simple_view?: boolean }>();
const locale = getLocale(params);
const messages = await loadLocale(locale).catch(() => ({}));
const boundActions = bindActionCreators({ fetchHiddenUsers, restoreCollapsedThreads }, reduxStore.dispatch);
const boundActions = bindActionCreators({ fetchHiddenUsers, restoreCollapsedThreads }, store.dispatch);
boundActions.fetchHiddenUsers();
boundActions.restoreCollapsedThreads();
@@ -45,7 +45,7 @@ async function init(): Promise<void> {
render(
<IntlProvider locale={locale} messages={messages}>
<Provider store={reduxStore}>{params.page === 'user-info' ? <UserInfo /> : <ConnectedRoot />}</Provider>
<Provider store={store}>{params.page === 'user-info' ? <UserInfo /> : <ConnectedRoot />}</Provider>
</IntlProvider>,
node
);
+2 -2
View File
@@ -1,4 +1,4 @@
import stubStore from '__stubs__/store';
import { mockStore } from '__stubs__/store';
import { LS_SORT_KEY } from 'common/constants';
import { updateSorting } from './actions';
@@ -7,7 +7,7 @@ import { COMMENTS_SET_SORT } from './types';
describe('Store comments actions', () => {
it('should save last sort to localstorage', async () => {
const newSort = '+controversy';
const store = stubStore({
const store = mockStore({
comments: {
sort: '+active',
},
+1 -1
View File
@@ -198,7 +198,7 @@ function sort(state: Sorting = getInitialSort(), action: COMMENTS_SET_SORT_ACTIO
}
}
export default combineReducers({
export const comments = combineReducers({
sort,
isFetching,
topComments,
+4 -5
View File
@@ -1,9 +1,9 @@
import { createStore, applyMiddleware, AnyAction, compose, combineReducers } from 'redux';
import thunk, { ThunkAction, ThunkDispatch } from 'redux-thunk';
import storeReducers from './reducers';
import { rootProvider } from './reducers';
import { ACTIONS } from './actions';
const reducers = combineReducers(storeReducers);
const reducers = combineReducers(rootProvider);
export type StoreState = ReturnType<typeof reducers>;
@@ -24,11 +24,10 @@ const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
: compose;
const store = createStore(reducers, composeEnhancers(middleware));
export const store = createStore(reducers, composeEnhancers(middleware));
if (process.env.NODE_ENV === 'development') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).ReduxStore = store;
}
export default store;
+3 -5
View File
@@ -1,4 +1,4 @@
import comments from './comments/reducers';
import * as comments from './comments/reducers';
import * as postInfo from './post-info/reducers';
import * as theme from './theme/reducers';
import * as user from './user/reducers';
@@ -6,13 +6,11 @@ import * as userInfo from './user-info/reducers';
import * as thread from './thread/reducers';
/** Merged store reducers */
const rootProvider = {
comments,
export const rootProvider = {
...comments,
...theme,
...postInfo,
...userInfo,
...thread,
...user,
};
export default rootProvider;
+8 -8
View File
@@ -1,4 +1,4 @@
import stubStore from '__stubs__/store';
import { mockStore } from '__stubs__/store';
import { User } from 'common/types';
import { LS_HIDDEN_USERS_KEY } from 'common/constants';
import { COMMENTS_PATCH } from 'store/comments/types';
@@ -9,7 +9,7 @@ import { USER_UNHIDE, USER_HIDE, USER_UNBAN, USER_BANLIST_SET, USER_HIDELIST_SET
describe('store user actions', () => {
test('fetchBlockedUsers', async () => {
const store = stubStore(INITIAL_STORE);
const store = mockStore(INITIAL_STORE);
await store.dispatch(fetchBlockedUsers());
@@ -19,7 +19,7 @@ describe('store user actions', () => {
});
test('fetchHiddenUsers', async () => {
const store = stubStore(INITIAL_STORE);
const store = mockStore(INITIAL_STORE);
await store.dispatch(fetchHiddenUsers());
@@ -30,7 +30,7 @@ describe('store user actions', () => {
test('fetchHiddenUsers with data', async () => {
const data = { '1': { id: '1' }, '2': { id: '2' } };
const store = stubStore(INITIAL_STORE);
const store = mockStore(INITIAL_STORE);
localStorage.setItem(LS_HIDDEN_USERS_KEY, JSON.stringify(data));
await store.dispatch(fetchHiddenUsers());
@@ -41,7 +41,7 @@ describe('store user actions', () => {
});
test('setVerifiedStatus', async () => {
const store = stubStore(INITIAL_STORE);
const store = mockStore(INITIAL_STORE);
await store.dispatch(setVerifiedStatus('1', true));
@@ -55,7 +55,7 @@ describe('store user actions', () => {
});
test('unblockUser', async () => {
const store = stubStore(INITIAL_STORE);
const store = mockStore(INITIAL_STORE);
await store.dispatch(unblockUser('1'));
@@ -71,7 +71,7 @@ describe('store user actions', () => {
});
test('hideUser', async () => {
const store = stubStore(INITIAL_STORE);
const store = mockStore(INITIAL_STORE);
await store.dispatch(hideUser({ id: '1' } as User));
@@ -84,7 +84,7 @@ describe('store user actions', () => {
});
test('unhideUser', async () => {
const store = stubStore(INITIAL_STORE);
const store = mockStore(INITIAL_STORE);
localStorage.setItem(LS_HIDDEN_USERS_KEY, JSON.stringify({ '1': { id: '1' } }));
await store.dispatch(unhideUser('1'));
+1 -1
View File
@@ -1,7 +1,7 @@
import * as api from 'common/api';
import { User, BlockedUser, BlockTTL } from 'common/types';
import { ttlToTime } from 'utils/ttl-to-time';
import getHiddenUsers from 'utils/get-hidden-users';
import { getHiddenUsers } from 'utils/get-hidden-users';
import { LS_HIDDEN_USERS_KEY } from 'common/constants';
import { setItem } from 'common/local-storage';
@@ -1,4 +1,4 @@
import capitalizeFirstLetter from './capitalize-first-letter';
import { capitalizeFirstLetter } from './capitalize-first-letter';
it('should capitalize first letter', () => {
expect(capitalizeFirstLetter('one')).toBe('One');
@@ -1,3 +1,3 @@
export default function capitalizeFirstLetter(str: string): string {
export function capitalizeFirstLetter(str: string): string {
return `${str.charAt(0).toLocaleUpperCase()}${str.slice(1)}`;
}
+1 -4
View File
@@ -1,9 +1,6 @@
type FnType<T extends unknown[]> = (...args: T) => unknown;
export default function debounce<T extends unknown[]>(
fn: FnType<T>,
wait = 1000
): (...args: Parameters<FnType<T>>) => void {
export function debounce<T extends unknown[]>(fn: FnType<T>, wait = 1000): (...args: Parameters<FnType<T>>) => void {
let timeout: number | undefined;
return function (this: unknown, ...args): void {
+1 -1
View File
@@ -1,6 +1,6 @@
import { LS_HIDDEN_USERS_KEY } from 'common/constants';
import getHiddenUsers from './get-hidden-users';
import { getHiddenUsers } from './get-hidden-users';
describe('getHiddenUsers', () => {
it('should get hidden users from local storage', async () => {
+1 -1
View File
@@ -2,7 +2,7 @@ import { User } from 'common/types';
import { getItem } from 'common/local-storage';
import { LS_HIDDEN_USERS_KEY } from 'common/constants';
export default function getHiddenUsers() {
export function getHiddenUsers() {
try {
const hiddenUsers: Record<string, User> = JSON.parse(getItem(LS_HIDDEN_USERS_KEY) || '{}');
-2
View File
@@ -1,2 +0,0 @@
// eslint-disable-next-line @typescript-eslint/no-empty-function
export default function noop(): void {}
+1 -1
View File
@@ -1,4 +1,4 @@
import parseQuery from './parseQuery';
import { parseQuery } from './parseQuery';
describe('parseQuery', () => {
it('should return empty object', () => {
+1 -1
View File
@@ -1,6 +1,6 @@
/** converts window.location.search into object */
export default function parseQuery<T extends {}>(search: string = window.location.search): T {
export function parseQuery<T extends {}>(search: string = window.location.search): T {
const params: { [key: string]: string } = {};
new URLSearchParams(search).forEach((value: string, key: string) => {
params[key] = value;
+1 -1
View File
@@ -15,7 +15,7 @@ export type Message =
*
* @returns request success of fail
*/
export default function postMessage(data: Message): boolean {
export function postMessage(data: Message): boolean {
if (!window.parent || window.parent === window) return false;
window.parent.postMessage(JSON.stringify(data), '*');
return true;
+1 -1
View File
@@ -1,4 +1,4 @@
export default function shallowCompare<T extends Record<string, unknown>>(a: T, b: T): boolean {
export function shallowCompare<T extends Record<string, unknown>>(a: T, b: T): boolean {
const entriesA = Object.entries(a);
const keysB = Object.keys(b);
if (entriesA.length !== keysB.length) return false;