update Preact to Preact X

This commit is contained in:
Vyrtsev Mikhail
2019-10-28 02:30:49 +03:00
parent 4240a4d6ee
commit 02b2895cac
50 changed files with 404 additions and 280 deletions
-18
View File
@@ -1,18 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
declare namespace JSX {
interface IntrinsicElements {
'markdown-toolbar': {
for: string;
children?: any;
className: string;
};
'md-bold': any;
'md-header': any;
'md-italic': any;
'md-quote': any;
'md-code': any;
'md-link': any;
'md-unordered-list': any;
'md-ordered-list': any;
}
}
+24
View File
@@ -0,0 +1,24 @@
/* 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' {
namespace JSXInternal {
interface IntrinsicElements {
'markdown-toolbar': {
for: string;
children?: any;
className: string;
};
'md-bold': any;
'md-header': any;
'md-italic': any;
'md-quote': any;
'md-code': any;
'md-link': any;
'md-unordered-list': any;
'md-ordered-list': any;
}
}
}
@@ -1,5 +1,5 @@
/** @jsx h */
import { h, Component, RenderableProps } from 'preact';
/** @jsx createElement */
import { createElement, Component, createRef } from 'preact';
import b from 'bem-react-helper';
import { Theme } from '@app/common/types';
@@ -17,7 +17,7 @@ interface State {
export class AnonymousLoginForm extends Component<Props, State> {
static usernameRegex = /^[a-zA-Z][\w ]+$/;
inputRef?: HTMLInputElement;
inputRef = createRef<HTMLInputElement>();
constructor(props: Props) {
super(props);
@@ -60,11 +60,12 @@ export class AnonymousLoginForm extends Component<Props, State> {
componentDidUpdate() {
setTimeout(() => {
this.inputRef && this.inputRef.focus();
this.inputRef.current && this.inputRef.current.focus();
}, 100);
}
render(props: RenderableProps<Props>) {
render() {
const props = this.props;
// TODO: will be great to `b` to accept `string | undefined | (string|undefined)[]` as classname
let className = b('auth-panel-anonymous-login-form', {}, { theme: props.theme });
if (props.className) {
@@ -77,7 +78,7 @@ export class AnonymousLoginForm extends Component<Props, State> {
<form className={className} onSubmit={this.onSubmit}>
<input
className="auth-panel-anonymous-login-form__input"
ref={ref => (this.inputRef = ref)}
ref={this.inputRef}
type="text"
placeholder="Username"
value={this.state.inputValue}
@@ -1,5 +1,5 @@
/** @jsx h */
import { h } from 'preact';
/** @jsx createElement */
import { createElement } from 'preact';
import { mount } from 'enzyme';
import { EmailLoginForm, Props, State } from './auth-panel__email-login-form';
import { User } from '@app/common/types';
@@ -1,10 +1,11 @@
/** @jsx h */
import { h, Component, RenderableProps } from 'preact';
/** @jsx createElement */
import { createElement, Component, createRef } from 'preact';
import { forwardRef } from 'preact/compat';
import b from 'bem-react-helper';
import { useSelector } from 'react-redux';
import { Theme, User } from '@app/common/types';
import { sendEmailVerificationRequest } from '@app/common/api';
import { extractErrorMessageFromResponse } from '@app/utils/errorUtils';
import { connect } from 'preact-redux';
import { getHandleClickProps } from '@app/common/accessibility';
import { sleep } from '@app/utils/sleep';
import TextareaAutosize from '@app/components/input/textarea-autosize';
@@ -13,12 +14,14 @@ const mapStateToProps = () => ({
sendEmailVerification: sendEmailVerificationRequest,
});
export type Props = {
interface OwnProps {
onSignIn(token: string): Promise<User | null>;
onSuccess?(user: User): Promise<void>;
theme: Theme;
className?: string;
} & ReturnType<typeof mapStateToProps>;
}
export type Props = OwnProps & ReturnType<typeof mapStateToProps>;
export interface State {
usernameValue: string;
@@ -33,8 +36,8 @@ export class EmailLoginForm extends Component<Props, State> {
static usernameRegex = /^[a-zA-Z][\w ]+$/;
static emailRegex = /[^@]+@[^.]+\..+/;
inputRef?: HTMLInputElement;
tokenRef?: TextareaAutosize;
inputRef = createRef<HTMLInputElement>();
tokenRef = createRef<TextareaAutosize>();
constructor(props: Props) {
super(props);
@@ -59,11 +62,11 @@ export class EmailLoginForm extends Component<Props, State> {
async focus() {
await sleep(100);
if (this.inputRef) {
this.inputRef.focus();
if (this.inputRef.current) {
this.inputRef.current.focus();
return;
}
this.tokenRef && this.tokenRef.textareaRef && this.tokenRef.textareaRef.select();
this.tokenRef.current && this.tokenRef.current.textareaRef && this.tokenRef.current.textareaRef.select();
}
async onVerificationSubmit(e: Event) {
@@ -73,7 +76,7 @@ export class EmailLoginForm extends Component<Props, State> {
await this.props.sendEmailVerification(this.state.usernameValue, this.state.addressValue);
this.setState({ verificationSent: true });
setTimeout(() => {
this.tokenRef && this.tokenRef.focus();
this.tokenRef.current && this.tokenRef.current.focus();
}, 100);
} catch (e) {
this.setState({ error: extractErrorMessageFromResponse(e) });
@@ -119,7 +122,7 @@ export class EmailLoginForm extends Component<Props, State> {
verificationSent: false,
});
setTimeout(() => {
this.inputRef && this.inputRef.focus();
this.inputRef.current && this.inputRef.current.focus();
}, 100);
}
@@ -141,11 +144,11 @@ export class EmailLoginForm extends Component<Props, State> {
componentDidMount() {
setTimeout(() => {
this.inputRef && this.inputRef.focus();
this.inputRef.current && this.inputRef.current.focus();
}, 100);
}
render(props: RenderableProps<Props>) {
render(props: Props) {
// TODO: will be great to `b` to accept `string | undefined | (string|undefined)[]` as classname
let className = b('auth-panel-email-login-form', {}, { theme: props.theme });
if (props.className) {
@@ -173,7 +176,7 @@ export class EmailLoginForm extends Component<Props, State> {
</span>
<input
className="auth-panel-email-login-form__input"
ref={ref => (this.inputRef = ref)}
ref={this.inputRef}
type="text"
placeholder="Username"
value={this.state.usernameValue}
@@ -193,7 +196,7 @@ export class EmailLoginForm extends Component<Props, State> {
title={form1InvalidReason || ''}
disabled={form1InvalidReason !== null}
/>
{this.state.error && <div class="auth-panel-email-login-form__error">{this.state.error}</div>}
{this.state.error && <div className="auth-panel-email-login-form__error">{this.state.error}</div>}
</form>
);
@@ -207,7 +210,7 @@ export class EmailLoginForm extends Component<Props, State> {
<TextareaAutosize
autofocus={true}
className="auth-panel-email-login-form__token-input"
ref={ref => (this.tokenRef = ref)}
ref={this.tokenRef}
placeholder="Token"
value={this.state.tokenValue}
onInput={this.onTokenChange}
@@ -221,15 +224,15 @@ export class EmailLoginForm extends Component<Props, State> {
title={form2InvalidReason || ''}
disabled={form2InvalidReason !== null}
/>
{this.state.error && <div class="auth-panel-email-login-form__error">{this.state.error}</div>}
{this.state.error && <div className="auth-panel-email-login-form__error">{this.state.error}</div>}
</form>
);
}
}
export const EmailLoginFormConnected = connect(
mapStateToProps,
null,
null,
{ withRef: true }
)(EmailLoginForm);
export type EmailLoginFormRef = EmailLoginForm;
export const EmailLoginFormConnected = forwardRef<EmailLoginForm, OwnProps>((props, ref) => {
const connectedProps = useSelector(mapStateToProps);
return <EmailLoginForm {...props} {...connectedProps} ref={ref} />;
});
@@ -1,20 +1,15 @@
/** @jsx h */
import { h } from 'preact';
/** @jsx createElement */
import { createElement, JSX, FunctionComponent } from 'preact';
import b from 'bem-react-helper';
import { Theme } from '@app/common/types';
import { exclude } from '@app/utils/exclude';
interface Props {
type Props = {
id: string;
theme: Theme;
}
} & JSX.HTMLAttributes;
export const UserID = (props: JSX.HTMLAttributes & Props) => (
<div
{...exclude(props, 'id', 'theme')}
className={b('auth-panel__user-id', {}, { theme: props.theme })}
title={props.id}
>
{props.id}
export const UserID: FunctionComponent<Props> = ({ id, theme, ...props }) => (
<div {...props} className={b('auth-panel__user-id', {}, { theme })} title={id}>
{id}
</div>
);
@@ -1,5 +1,5 @@
/** @jsx h */
import { h } from 'preact';
/** @jsx createElement */
import { createElement } from 'preact';
import { mount } from 'enzyme';
import { Props, AuthPanel } from './auth-panel';
import { User, PostInfo } from '../../common/types';
@@ -1,5 +1,5 @@
/** @jsx h */
import { h, Component, RenderableProps } from 'preact';
/** @jsx createElement */
import { createElement, Component, createRef } from 'preact';
import b from 'bem-react-helper';
import { PROVIDER_NAMES, IS_STORAGE_AVAILABLE, IS_THIRD_PARTY } from '@app/common/constants';
@@ -11,11 +11,12 @@ import Dropdown, { DropdownItem } from '@app/components/dropdown';
import { Button } from '@app/components/button';
import { UserID } from './__user-id';
import { AnonymousLoginForm } from './__anonymous-login-form';
import { EmailLoginForm, EmailLoginFormConnected } from './__email-login-form';
import { EmailLoginFormConnected } from './__email-login-form';
import { StoreState } from '@app/store';
import { ProviderState } from '@app/store/provider/reducers';
import debounce from '@app/utils/debounce';
import postMessage from '@app/utils/postMessage';
import { EmailLoginFormRef } from './__email-login-form/auth-panel__email-login-form';
export interface Props {
user: User | null;
@@ -44,7 +45,7 @@ interface State {
}
export class AuthPanel extends Component<Props, State> {
emailLoginRef?: EmailLoginForm;
emailLoginRef = createRef<EmailLoginFormRef>();
constructor(props: Props) {
super(props);
@@ -85,7 +86,7 @@ export class AuthPanel extends Component<Props, State> {
}, 100);
onEmailTitleClick() {
this.emailLoginRef && this.emailLoginRef.focus();
this.emailLoginRef.current && this.emailLoginRef.current.focus();
}
onSortChange(e: Event) {
@@ -208,7 +209,7 @@ export class AuthPanel extends Component<Props, State> {
>
<DropdownItem>
<EmailLoginFormConnected
ref={ref => (this.emailLoginRef = ref ? ref.getWrappedInstance() : null)}
ref={this.emailLoginRef}
onSignIn={this.onEmailSignIn}
theme={this.props.theme}
className="auth-panel__email-login-form"
@@ -305,7 +306,7 @@ export class AuthPanel extends Component<Props, State> {
<div className="auth-panel__column">
Disable third-party cookies blocking to sign in or open comments in{' '}
<a
class="auth-panel__pseudo-link"
className="auth-panel__pseudo-link"
href={`${window.location.origin}/web/comments.html${window.location.search}`}
target="_blank"
>
@@ -373,7 +374,7 @@ export class AuthPanel extends Component<Props, State> {
);
};
render(props: RenderableProps<Props>, { isBlockedVisible }: State) {
render(props: Props, { isBlockedVisible }: State) {
const {
user,
postInfo: { read_only },
@@ -1,5 +1,5 @@
/** @jsx h */
import { h } from 'preact';
/** @jsx createElement */
import { createElement, JSX } from 'preact';
import b from 'bem-react-helper';
import { Theme } from '@app/common/types';
+6 -6
View File
@@ -1,11 +1,11 @@
/** @jsx h */
import { Component, h, RenderableProps } from 'preact';
/** @jsx createElement */
import { createElement, JSX, Component } from 'preact';
import b from 'bem-react-helper';
import noop from '@app/utils/noop';
import { Theme } from '@app/common/types';
interface Props {
type Props = {
type?: string;
kind?: string;
theme: Theme;
@@ -14,14 +14,14 @@ interface Props {
onClick?: (e: MouseEvent) => void;
onFocus?: (e: FocusEvent) => void;
onBlur?: (e: FocusEvent) => void;
}
} & JSX.HTMLAttributes;
interface State {
isClicked: boolean;
isFocused: boolean;
}
export class Button extends Component<JSX.HTMLAttributes & Props, State> {
export class Button extends Component<Props, State> {
constructor(props: Props) {
super(props);
@@ -62,7 +62,7 @@ export class Button extends Component<JSX.HTMLAttributes & Props, State> {
this.props.onFocus!(e);
}
render(props: RenderableProps<JSX.HTMLAttributes & Props>, state: State) {
render(props: Props, state: State) {
const { children, className } = props;
const { isClicked, isFocused } = state;
@@ -1,5 +1,5 @@
/** @jsx h */
import { h } from 'preact';
/** @jsx createElement */
import { createElement } from 'preact';
import { mount } from 'enzyme';
import { Props, Comment } from './comment';
import { User, Comment as CommentType, PostInfo } from '@app/common/types';
+9 -7
View File
@@ -1,8 +1,8 @@
/** @jsx h */
/** @jsx createElement */
import './styles';
import { h, Component, RenderableProps } from 'preact';
import { createElement, JSX, Component, createRef } from 'preact';
import b from 'bem-react-helper';
import { getHandleClickProps } from '@app/common/accessibility';
@@ -72,7 +72,7 @@ export interface State {
export class Comment extends Component<Props, State> {
votingPromise: Promise<unknown>;
/** comment text node. Used in comment text copying */
textNode?: HTMLDivElement;
textNode = createRef<HTMLDivElement>();
constructor(props: Props) {
super(props);
@@ -305,7 +305,7 @@ export class Comment extends Component<Props, State> {
copyComment = () => {
const username = this.props.data.user.name;
const time = this.props.data.time;
const text = this.textNode!.textContent || '';
const text = this.textNode.current!.textContent || '';
copy(`<b>${username}</b>&nbsp;${time}<br>${text.replace(/\n+/g, '<br>')}`);
@@ -447,7 +447,7 @@ export class Comment extends Component<Props, State> {
return controls;
};
render(props: RenderableProps<Props>, state: State) {
render(props: Props, state: State) {
const isAdmin = this.isAdmin();
const isGuest = this.isGuest();
const isCurrentUser = this.isCurrentUser();
@@ -547,7 +547,9 @@ export class Comment extends Component<Props, State> {
}
if (!props.editMode && this.props.inView === false) {
const [width, height] = this.base ? [this.base.scrollWidth, this.base.scrollHeight] : [100, 100];
const [width, height] = this.base
? [(this.base as Element).scrollWidth, (this.base as Element).scrollHeight]
: [100, 100];
return (
<article
id={props.disabled ? undefined : `${COMMENT_NODE_CLASSNAME_PREFIX}${o.id}`}
@@ -670,7 +672,7 @@ export class Comment extends Component<Props, State> {
{(!props.collapsed || props.view === 'pinned') && (
<div
className={b('comment__text', { mix: b('raw-content', {}, { theme: props.theme }) })}
ref={r => (this.textNode = r)}
ref={this.textNode}
dangerouslySetInnerHTML={{ __html: o.text }}
/>
)}
@@ -3,11 +3,15 @@
* and should be importded explicitly
*/
/** @jsx createElement */
import './styles';
import { createElement, FunctionComponent } from 'preact';
import { Comment as CommentType } from '@app/common/types';
import { connect } from 'preact-redux';
import { useStore } from 'react-redux';
import { StoreState } from '@app/store';
import {
@@ -26,27 +30,30 @@ import { getCommentMode } from '@app/store/comments/getters';
import { uploadImage, getPreview } from '@app/common/api';
import { getThreadIsCollapsed } from '@app/store/thread/getters';
import { bindActions } from '@app/utils/actionBinder';
import { useActions } from '@app/hooks/useAction';
type ProvidedProps = Pick<
Props,
| 'editMode'
| 'user'
| 'isUserBanned'
| 'post_info'
| 'isCommentsDisabled'
| 'theme'
| 'collapsed'
| 'getPreview'
| 'uploadImage'
>;
const mapStateToProps = (state: StoreState, cprops: { data: CommentType }) => {
const props: Pick<
Props,
| 'editMode'
| 'user'
| 'isUserBanned'
| 'post_info'
| 'isCommentsDisabled'
| 'theme'
| 'collapsed'
| 'getPreview'
| 'uploadImage'
> = {
editMode: getCommentMode(state, cprops.data.id),
const props: ProvidedProps = {
editMode: getCommentMode(cprops.data.id)(state),
user: state.user,
isUserBanned: cprops.data.user.block || state.bannedUsers.find(u => u.id === cprops.data.user.id) !== undefined,
post_info: state.info,
isCommentsDisabled: state.info.read_only || false,
theme: state.theme,
collapsed: getThreadIsCollapsed(state, cprops.data),
collapsed: getThreadIsCollapsed(cprops.data)(state),
getPreview,
uploadImage,
};
@@ -67,8 +74,8 @@ export const boundActions = bindActions({
setVerifyStatus: setVerifiedStatus,
});
/** Comment component connected to redux */
export const ConnectedComment = connect(
mapStateToProps,
boundActions as Partial<typeof boundActions>
)(Comment);
export const ConnectedComment: FunctionComponent<Omit<Props, keyof (ProvidedProps & typeof bindActions)>> = props => {
const providedProps = mapStateToProps(useStore().getState(), props);
const actions = useActions(boundActions);
return <Comment {...props} {...providedProps} {...actions} />;
};
+6 -6
View File
@@ -1,5 +1,5 @@
/** @jsx h */
import { h, Component, RenderableProps } from 'preact';
/** @jsx createElement */
import { createElement, JSX, Component, createRef } from 'preact';
import { exclude } from '@app/utils/exclude';
type Props = {
@@ -14,7 +14,7 @@ interface State {
/** Component which uses plain DOM mutation instead of rerendering react reactive reactivity */
export default class Countdown extends Component<Props, State> {
elemRef?: HTMLSpanElement;
elemRef = createRef<HTMLSpanElement>();
intervalID?: number;
constructor(props: Props) {
super(props);
@@ -41,7 +41,7 @@ export default class Countdown extends Component<Props, State> {
tick() {
if (this.elemRef) {
const value = Math.max(0, (this.state.time - new Date().getTime()) / 1000).toFixed(0);
this.elemRef!.innerText = value;
this.elemRef.current!.innerText = value;
if (value === '0') {
this.props.onTimePassed && this.props.onTimePassed();
window.clearInterval(this.intervalID);
@@ -56,7 +56,7 @@ export default class Countdown extends Component<Props, State> {
this.tick();
}, 1000);
}
render(props: RenderableProps<Props>) {
return <span {...exclude(props, 'time', 'onTimePassed')} ref={ref => (this.elemRef = ref)} />;
render(props: Props) {
return <span {...exclude(props, 'time', 'onTimePassed')} ref={this.elemRef} />;
}
}
@@ -1,5 +1,5 @@
/** @jsx h */
import { h, RenderableProps } from 'preact';
/** @jsx createElement */
import { createElement, JSX, RenderableProps } from 'preact';
import b from 'bem-react-helper';
interface Props {
+18 -17
View File
@@ -1,5 +1,5 @@
/** @jsx h */
import { Component, h, RenderableProps } from 'preact';
/** @jsx createElement */
import { createElement, Component, createRef } from 'preact';
import b from 'bem-react-helper';
import { Button } from '@app/components/button';
@@ -24,7 +24,7 @@ interface State {
}
export default class Dropdown extends Component<Props, State> {
rootNode?: HTMLDivElement;
rootNode = createRef<HTMLDivElement>();
constructor(props: Props) {
super(props);
@@ -52,10 +52,10 @@ export default class Dropdown extends Component<Props, State> {
await this.__adjustDropDownContent();
if (isActive) {
this.__onOpen();
this.props.onOpen && this.props.onOpen(this.rootNode!);
this.props.onOpen && this.props.onOpen(this.rootNode.current!);
} else {
this.__onClose();
this.props.onClose && this.props.onClose(this.rootNode!);
this.props.onClose && this.props.onClose(this.rootNode.current!);
}
if (this.props.onTitleClick) {
@@ -71,8 +71,8 @@ export default class Dropdown extends Component<Props, State> {
__onOpen() {
const isChildOfDropDown = (() => {
if (!this.rootNode) return false;
let parent = this.rootNode.parentElement!;
if (!this.rootNode.current) return false;
let parent = this.rootNode.current.parentElement!;
while (parent !== document.body) {
if (parent.classList.contains('dropdown')) return true;
parent = parent.parentElement!;
@@ -87,10 +87,10 @@ export default class Dropdown extends Component<Props, State> {
let prevDcBottom: number | null = null;
this.checkInterval = window.setInterval(() => {
if (!this.rootNode || !this.state.isActive) return;
if (!this.rootNode.current || !this.state.isActive) return;
const windowHeight = window.innerHeight;
const dcBottom = (() => {
const dc = Array.from(this.rootNode.children).find(c => c.classList.contains('dropdown__content'));
const dc = Array.from(this.rootNode.current.children).find(c => c.classList.contains('dropdown__content'));
if (!dc) return 0;
const rect = dc.getBoundingClientRect();
return window.scrollY + Math.abs(rect.top) + dc.scrollHeight + 10;
@@ -111,8 +111,8 @@ export default class Dropdown extends Component<Props, State> {
}
async __adjustDropDownContent() {
if (!this.rootNode) return;
const dc = this.rootNode.querySelector<HTMLDivElement>('.dropdown__content');
if (!this.rootNode.current) return;
const dc = this.rootNode.current.querySelector<HTMLDivElement>('.dropdown__content');
if (!dc) return;
await sleep(10);
const rect = dc.getBoundingClientRect();
@@ -144,14 +144,14 @@ export default class Dropdown extends Component<Props, State> {
},
() => {
this.__onClose();
this.props.onClose && this.props.onClose(this.rootNode!);
this.props.onClose && this.props.onClose(this.rootNode.current!);
}
);
} catch (e) {}
}
onOutsideClick(e: MouseEvent) {
if (!this.rootNode || this.rootNode.contains(e.target as Node) || !this.state.isActive) return;
if (!this.rootNode.current || this.rootNode.current.contains(e.target as Node) || !this.state.isActive) return;
this.setState(
{
contentTranslateX: 0,
@@ -159,7 +159,7 @@ export default class Dropdown extends Component<Props, State> {
},
() => {
this.__onClose();
this.props.onClose && this.props.onClose(this.rootNode!);
this.props.onClose && this.props.onClose(this.rootNode.current!);
}
);
}
@@ -176,11 +176,12 @@ export default class Dropdown extends Component<Props, State> {
window.removeEventListener('message', this.receiveMessage);
}
render(props: RenderableProps<Props>, { isActive }: State) {
const { title, titleClass, heading, children, mix } = props;
render() {
const { title, titleClass, heading, children, mix, theme } = this.props;
const { isActive } = this.state;
return (
<div className={b('dropdown', { mix }, { theme: props.theme, active: isActive })} ref={r => (this.rootNode = r)}>
<div className={b('dropdown', { mix }, { theme, active: isActive })} ref={this.rootNode}>
<Button
aria-haspopup="listbox"
aria-expanded={isActive && 'true'}
+13 -15
View File
@@ -1,10 +1,10 @@
/** @jsx h */
/** @jsx createElement */
/* styles imports */
import '@app/components/raw-content';
import './styles';
import { h, Component, RenderableProps } from 'preact';
import { createElement, Component, createRef } from 'preact';
import b, { Mix } from 'bem-react-helper';
import { User, Theme, Image, ApiError } from '@app/common/types';
@@ -66,8 +66,9 @@ const ImageMimeRegex = /image\//i;
export class Input extends Component<Props, State> {
/** reference to textarea element */
textAreaRef?: TextareaAutosize;
textAreaRef = createRef<TextareaAutosize>();
textareaId: string;
constructor(props: Props) {
super(props);
textareaId = textareaId + 1;
@@ -98,7 +99,7 @@ export class Input extends Component<Props, State> {
componentWillReceiveProps(nextProps: Props) {
if (nextProps.value !== this.props.value) {
this.setState({ text: nextProps.value || '' });
this.props.autofocus && this.textAreaRef && this.textAreaRef.focus();
this.props.autofocus && this.textAreaRef.current && this.textAreaRef.current.focus();
}
}
@@ -242,14 +243,14 @@ export class Input extends Component<Props, State> {
/** performs upload process */
async uploadImages(files: File[]) {
if (!this.props.uploadImage) return;
if (!this.textAreaRef) return;
if (!this.textAreaRef.current) return;
/** Human readable image size limit, i.e 5MB */
const maxImageSizeString = (StaticStore.config.max_image_size / 1024 / 1024).toFixed(2) + 'MB';
/** upload delay to avoid server rate limiter */
const uploadDelay = 5000;
const isSelectionSupported = this.textAreaRef.isSelectionSupported();
const isSelectionSupported = this.textAreaRef.current.isSelectionSupported();
this.setState({
errorLock: true,
@@ -297,7 +298,7 @@ export class Input extends Component<Props, State> {
const uploadPlaceholder = `${placeholderStart}![uploading ${file.name}...]()`;
const uploadPlaceholderLength = uploadPlaceholder.length;
const selection = this.textAreaRef.getSelection();
const selection = this.textAreaRef.current.getSelection();
/** saved selection in case of error */
const originalText = this.state.text;
const restoreSelection = async () => {
@@ -306,7 +307,7 @@ export class Input extends Component<Props, State> {
});
/** sleeping awhile so textarea catch state change and its selection */
await sleep(100);
this.textAreaRef!.setSelection(selection);
this.textAreaRef.current!.setSelection(selection);
};
if (file.size > StaticStore.config.max_image_size) {
@@ -335,16 +336,13 @@ export class Input extends Component<Props, State> {
/** sleeping awhile so textarea catch state change and its selection */
await sleep(100);
const selectionPointer = selection[0] + markdownString.length;
this.textAreaRef.setSelection([selectionPointer, selectionPointer]);
this.textAreaRef.current.setSelection([selectionPointer, selectionPointer]);
}
this.setState({ errorLock: false, isDisabled: false, buttonText: null });
}
render(
props: RenderableProps<Props>,
{ isDisabled, isErrorShown, errorMessage, preview, maxLength, text, buttonText }: State
) {
render(props: Props, { isDisabled, isErrorShown, errorMessage, preview, maxLength, text, buttonText }: State) {
const charactersLeft = maxLength - text.length;
errorMessage = props.errorMessage || errorMessage;
const label = buttonText || Labels[props.mode || 'main'];
@@ -374,7 +372,7 @@ export class Input extends Component<Props, State> {
<TextareaAutosize
id={this.textareaId}
onPaste={this.onPaste}
ref={ref => (this.textAreaRef = ref)}
ref={this.textAreaRef}
className="input__field"
placeholder="Your comment here"
value={text}
@@ -412,7 +410,7 @@ export class Input extends Component<Props, State> {
{props.mode === 'main' && (
<div className="input__rss">
<div class="input__markdown">
<div className="input__markdown">
Styling with{' '}
<a className="input__markdown-link" target="_blank" href="markdown-help.html">
Markdown
@@ -1,5 +1,5 @@
/** @jsx h */
import { h } from 'preact';
/** @jsx createElement */
import { createElement } from 'preact';
export default function BoldIcon() {
return (
@@ -1,5 +1,5 @@
/** @jsx h */
import { h } from 'preact';
/** @jsx createElement */
import { createElement } from 'preact';
export default function CodeIcon() {
return (
@@ -1,5 +1,5 @@
/** @jsx h */
import { h } from 'preact';
/** @jsx createElement */
import { createElement } from 'preact';
export default function HeaderIcon() {
return (
@@ -1,5 +1,5 @@
/** @jsx h */
import { h } from 'preact';
/** @jsx createElement */
import { createElement } from 'preact';
export default function ImageIcon() {
return (
@@ -1,5 +1,5 @@
/** @jsx h */
import { h } from 'preact';
/** @jsx createElement */
import { createElement } from 'preact';
export default function ItalicIcon() {
return (
@@ -1,5 +1,5 @@
/** @jsx h */
import { h } from 'preact';
/** @jsx createElement */
import { createElement } from 'preact';
export default function LinkIcon() {
return (
@@ -1,5 +1,5 @@
/** @jsx h */
import { h } from 'preact';
/** @jsx createElement */
import { createElement } from 'preact';
export default function OrderedListIcon() {
return (
@@ -1,5 +1,5 @@
/** @jsx h */
import { h } from 'preact';
/** @jsx createElement */
import { createElement } from 'preact';
export default function QuoteIcon() {
return (
@@ -1,5 +1,5 @@
/** @jsx h */
import { h } from 'preact';
/** @jsx createElement */
import { createElement } from 'preact';
export default function UnorderedListIcon() {
return (
@@ -1,5 +1,5 @@
/** @jsx h */
import { h, Component, RenderableProps } from 'preact';
/** @jsx createElement */
import { createElement, Component } from 'preact';
import '@github/markdown-toolbar-element';
import BoldIcon from './markdown-toolbar-icons/bold-icon';
import HeaderIcon from './markdown-toolbar-icons/header-icon';
@@ -48,7 +48,7 @@ export default class MarkdownToolbar extends Component<Props> {
await this.props.uploadImages(files);
currentTarget.value = null;
}
render(props: RenderableProps<Props>) {
render(props: Props) {
return (
<markdown-toolbar className="input__toolbar" for={props.textareaId}>
<div className="input__toolbar-group">
@@ -74,7 +74,7 @@ export default class MarkdownToolbar extends Component<Props> {
</md-link>
{this.props.allowUpload ? (
<label className="input__toolbar-item" title={attachImageLabel} aria-label={attachImageLabel}>
<input multiple class="input__toolbar-file-input" type="file" onChange={this.uploadImages} />
<input multiple className="input__toolbar-file-input" type="file" onChange={this.uploadImages} />
<ImageIcon />
</label>
) : null}
@@ -1,5 +1,5 @@
/** @jsx h */
import { h, Component, RenderableProps } from 'preact';
/** @jsx createElement */
import { createElement, JSX, Component } from 'preact';
type Props = JSX.HTMLAttributes & {
autofocus: boolean;
@@ -65,7 +65,7 @@ export default class TextareaAutosize extends Component<Props> {
this.textareaRef.style.height = `${this.textareaRef.scrollHeight}px`;
}
}
render(props: RenderableProps<Props>) {
render(props: Props) {
return (
// We set text as a child of textarea and not in value property for a reason.
// It's a workaround for the bug described here https://github.com/developit/preact/issues/326
@@ -1,5 +1,5 @@
/** @jsx h */
import { h } from 'preact';
/** @jsx createElement */
import { createElement } from 'preact';
import { NODE_ID } from '@app/common/constants';
import { Comment as CommentType } from '@app/common/types';
@@ -1,5 +1,5 @@
/** @jsx h */
import { h } from 'preact';
/** @jsx createElement */
import { createElement } from 'preact';
import { mount } from 'enzyme';
import Preloader from './preloader';
@@ -1,5 +1,5 @@
/** @jsx h */
import { h } from 'preact';
/** @jsx createElement */
import { createElement, JSX } from 'preact';
import b, { Mix } from 'bem-react-helper';
type Props = JSX.HTMLAttributes & {
@@ -1,8 +1,8 @@
import { Component } from 'preact';
import { Component, JSX } from 'preact';
import { sleep } from '@app/utils/sleep';
interface Props {
children: (props: { inView: boolean; ref: (ref: Component) => Component }) => JSX.Element;
children: (props: { inView: boolean; ref: (ref: Component) => unknown }) => JSX.Element;
}
interface State {
@@ -52,7 +52,7 @@ export class InView extends Component<Props, State> {
const el = ref ? ref.base : undefined;
if (el === this.state.ref) return;
this.setState({
ref: ref ? ref.base : undefined,
ref: ref ? (ref.base as Element) : undefined,
});
};
@@ -65,8 +65,7 @@ export class InView extends Component<Props, State> {
render() {
const props = { inView: this.state.inView, ref: this.refSetter };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const r = (this.props.children as any)[0](props);
const r = this.props.children(props);
return r;
}
}
+10 -8
View File
@@ -1,6 +1,6 @@
/** @jsx h */
import { h, Component, RenderableProps } from 'preact';
import { connect } from 'preact-redux';
/** @jsx createElement */
import { createElement, Component, FunctionComponent } from 'preact';
import { useSelector } from 'react-redux';
import b from 'bem-react-helper';
import { User, Sorting, AuthProvider } from '@app/common/types';
@@ -42,6 +42,7 @@ import { uploadImage, getPreview } from '@app/common/api';
import { isUserAnonymous } from '@app/utils/isUserAnonymous';
import { bindActions } from '@app/utils/actionBinder';
import postMessage from '@app/utils/postMessage';
import { useActions } from '@app/hooks/useAction';
const mapStateToProps = (state: StoreState) => ({
user: state.user,
@@ -201,7 +202,7 @@ export class Root extends Component<Props, State> {
return isUserAnonymous(this.props.user);
}
render(props: RenderableProps<Props>, { isLoaded, isCommentsListLoading, commentsShown }: State) {
render(props: Props, { isLoaded, isCommentsListLoading, commentsShown }: State) {
if (!isLoaded) {
return (
<div id={NODE_ID}>
@@ -325,7 +326,8 @@ export class Root extends Component<Props, State> {
}
/** Root component connected to redux */
export const ConnectedRoot = connect(
mapStateToProps,
boundActions
)(Root);
export const ConnectedRoot: FunctionComponent = () => {
const props = useSelector(mapStateToProps);
const actions = useActions(boundActions);
return <Root {...props} {...actions} />;
};
@@ -1,5 +1,5 @@
/** @jsx h */
import { h, Component, RenderableProps } from 'preact';
/** @jsx createElement */
import { createElement, Component } from 'preact';
import b from 'bem-react-helper';
import { User, BlockedUser, Theme, BlockTTL } from '@app/common/types';
@@ -29,7 +29,7 @@ interface State {
unhiddenUsers: (User['id'])[];
}
export default class BlockedUsers extends Component<Props, State> {
export default class Settings extends Component<Props, State> {
constructor(props: Props) {
super(props);
@@ -74,7 +74,7 @@ export default class BlockedUsers extends Component<Props, State> {
return false;
};
render({ user, theme }: RenderableProps<Props>, { blockedUsers, unblockedUsers, unhiddenUsers }: State) {
render({ user, theme }: Props, { blockedUsers, unblockedUsers, unhiddenUsers }: State) {
const hiddenUsersList = Object.values(this.state.hiddenUsers);
return (
<div className={b('settings', {}, { theme })}>
+12 -7
View File
@@ -1,6 +1,6 @@
/** @jsx h */
import { h, RenderableProps } from 'preact';
import { connect } from 'preact-redux';
/** @jsx createElement */
import { createElement, RenderableProps, FunctionComponent } from 'preact';
import { useStore } from 'react-redux';
import b from 'bem-react-helper';
import { ConnectedComment as Comment } from '@app/components/comment/connected-comment';
@@ -14,20 +14,22 @@ const mapStateToProps = (state: StoreState, props: { id: CommentInterface['id']
return {
comment,
childs: state.childComments[props.id],
collapsed: getThreadIsCollapsed(state, comment),
collapsed: getThreadIsCollapsed(comment)(state),
isCommentsDisabled: !!state.info.read_only,
theme: state.theme,
};
};
type Props = {
interface OwnProps {
id: CommentInterface['id'];
childs?: (CommentInterface['id'])[];
level: number;
mix?: string;
getPreview(text: string): Promise<string>;
} & ReturnType<typeof mapStateToProps>;
}
type Props = OwnProps & ReturnType<typeof mapStateToProps>;
function Thread(props: RenderableProps<Props>) {
const { collapsed, comment, childs, level, theme } = props;
@@ -67,4 +69,7 @@ function Thread(props: RenderableProps<Props>) {
);
}
export const ConnectedThread = connect(mapStateToProps)(Thread);
export const ConnectedThread: FunctionComponent<OwnProps> = props => {
const providedProps = mapStateToProps(useStore().getState(), props);
return <Thread {...props} {...providedProps} />;
};
@@ -1,5 +1,5 @@
/** @jsx h */
import { h } from 'preact';
/** @jsx createElement */
import { createElement } from 'preact';
import { Comment as CommentType } from '@app/common/types';
+20 -23
View File
@@ -1,9 +1,9 @@
/** @jsx h */
import { h, Component, RenderableProps } from 'preact';
/** @jsx createElement */
import { createElement, JSX, Component, FunctionComponent } from 'preact';
import b from 'bem-react-helper';
import { connect } from 'preact-redux';
import { useSelector } from 'react-redux';
import { StoreState, StoreDispatch } from '@app/store';
import { StoreState } from '@app/store';
import { Comment } from '@app/common/types';
import { fetchInfo } from '@app/store/user-info/actions';
import { userInfo } from '@app/common/user-info-settings';
@@ -11,11 +11,14 @@ import { userInfo } from '@app/common/user-info-settings';
import LastCommentsList from './last-comments-list';
import { AvatarIcon } from '../avatar-icon';
import postMessage from '@app/utils/postMessage';
import { bindActions } from '@app/utils/actionBinder';
import { useActions } from '@app/hooks/useAction';
interface Props {
const boundActions = bindActions({ fetchInfo });
type Props = {
comments: Comment[] | null;
fetchInfo: () => Promise<Comment[] | null>;
}
} & typeof boundActions;
interface State {
isLoading: boolean;
@@ -44,9 +47,10 @@ class UserInfo extends Component<Props, State> {
document.removeEventListener('keydown', UserInfo.onKeyDown);
}
render(props: RenderableProps<Props>, state: State): JSX.Element | null {
render(): JSX.Element | null {
const user = userInfo;
const { comments = [] } = props;
const { comments = [] } = this.props;
const { isLoading } = this.state;
// TODO: handle
if (!user) {
@@ -59,7 +63,7 @@ class UserInfo extends Component<Props, State> {
<p className="user-info__title">Last comments by {user.name}</p>
<p className="user-info__id">{user.id}</p>
{!!comments && <LastCommentsList isLoading={state.isLoading} comments={comments} />}
{!!comments && <LastCommentsList isLoading={isLoading} comments={comments} />}
</div>
);
}
@@ -76,17 +80,10 @@ class UserInfo extends Component<Props, State> {
}
}
const mapDispatchToProps = (dispatch: StoreDispatch) => ({
fetchInfo: () => dispatch(fetchInfo()),
});
const commentsSelector = (state: StoreState) => state.userComments![userInfo.id!];
export const ConnectedUserInfo = connect(
(
state: StoreState
): {
comments: Comment[] | null;
} => ({
comments: state.userComments![userInfo.id!],
}),
mapDispatchToProps
)(UserInfo);
export const ConnectedUserInfo: FunctionComponent = () => {
const comments = useSelector(commentsSelector);
const actions = useActions(boundActions);
return <UserInfo comments={comments} {...actions} />;
};
@@ -1,15 +0,0 @@
/** @jsx h */
import { AnyComponent } from 'preact';
import { connect } from 'preact-redux';
import { StoreState } from '@app/store';
import { Theme } from '@app/common/types';
/**
* Connects redux theme property to component's
*/
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function withTheme<P extends { theme: Theme }, S extends object>(PlainComponent: AnyComponent<P, S>) {
return connect((state: StoreState) => ({ theme: state.theme }))(PlainComponent);
}
export default withTheme;
@@ -0,0 +1,23 @@
/** @jsx createElement */
import { createElement, FunctionComponent, ComponentType } from 'preact';
import { useSelector } from 'react-redux';
import { StoreState } from '@app/store';
import { Theme } from '@app/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 o: 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)} />;
};
o.displayName = `withTheme(${PlainComponent.displayName || PlainComponent.name})`;
return o;
}
export default withTheme;
+32
View File
@@ -0,0 +1,32 @@
import { useCallback, useMemo } from 'preact/compat';
import { useDispatch } from 'react-redux';
import { BoundActionCreator, BoundActionCreators } from '@app/utils/actionBinder';
/* eslint-disable @typescript-eslint/no-explicit-any */
/** binds actions to dispatch */
export const useActions = <Actions extends { [key: string]: Function }>(
actions: Actions
): BoundActionCreators<Actions> => {
const dispatch = useDispatch();
return useMemo(
() =>
Object.entries(actions).reduce<BoundActionCreator<Actions>>(
(result, [key, fn]) => {
(result as any)[key] = (...args: any[]) => dispatch(fn(...args));
return result;
},
{} as any
),
[dispatch, ...Object.values(actions)]
) as any;
};
export const useAction = <Action extends Function>(action: Action): BoundActionCreator<Action> => {
const dispatch = useDispatch();
return useCallback(((...args: any[]) => dispatch(action(...args))) as any, [dispatch, action]);
};
/* eslint-enable @typescript-eslint/no-explicit-any */
+2 -3
View File
@@ -1,10 +1,9 @@
/* eslint-disable no-console, @typescript-eslint/camelcase */
/** @jsx h */
/** @jsx createElement */
declare let remark_config: LastCommentsConfig;
import loadPolyfills from '@app/common/polyfills';
import '@app/utils/patchPreactContext';
import { h, render } from 'preact';
import { createElement, render } from 'preact';
import 'preact/debug';
import { getLastComments } from './common/api';
import { LastCommentsConfig } from '@app/common/config-types';
+3 -6
View File
@@ -1,11 +1,10 @@
/* eslint-disable no-console */
/** @jsx h */
/** @jsx createElement */
import loadPolyfills from '@app/common/polyfills';
import '@app/utils/patchPreactContext';
import { h, render } from 'preact';
import { createElement, render } from 'preact';
import 'preact/debug';
import { Provider } from 'preact-redux';
import { Provider } from 'react-redux';
import { ConnectedRoot } from '@app/components/root';
import { UserInfo } from '@app/components/user-info';
import reduxStore from '@app/store';
@@ -69,7 +68,6 @@ async function init(): Promise<void> {
</Provider>
</div>
</div>,
node.parentElement!,
node
);
} else {
@@ -77,7 +75,6 @@ async function init(): Promise<void> {
<Provider store={reduxStore}>
<ConnectedRoot />
</Provider>,
node.parentElement!,
node
);
}
+1 -1
View File
@@ -1,7 +1,7 @@
import { Comment, CommentMode } from '@app/common/types';
import { StoreState } from '../index';
export const getCommentMode = (state: StoreState, id: Comment['id']): CommentMode => {
export const getCommentMode = (id: Comment['id']) => (state: StoreState): CommentMode => {
if (state.activeComment === null) {
return CommentMode.None;
}
+1 -1
View File
@@ -3,7 +3,7 @@ import { StaticStore } from '@app/common/static_store';
import { StoreState } from '../index';
export const getThreadIsCollapsed = (state: StoreState, comment: Comment): boolean => {
export const getThreadIsCollapsed = (comment: Comment) => (state: StoreState): boolean => {
const collapsed = state.collapsedThreads[comment.id];
if (collapsed !== null && collapsed !== undefined) {
-7
View File
@@ -1,7 +0,0 @@
// hack for https://github.com/developit/preact-compat/issues/475
// should be changed when preact@10 is available
import React from 'preact';
import { createContext } from 'preact-context';
React.createContext = createContext;
+2
View File
@@ -10,6 +10,8 @@ module.exports = {
moduleNameMapper: {
'\\.scss$': '<rootDir>/app/testUtils/mockStyles.js',
'@app/(.*)': '<rootDir>/app/$1',
'^react$': 'preact/compat',
'^react-dom$': 'preact/compat',
},
setupFilesAfterEnv: ['<rootDir>/app/testUtils/index.ts'],
};
+84 -14
View File
@@ -1793,6 +1793,15 @@
"@types/node": "*"
}
},
"@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"requires": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"@types/istanbul-lib-coverage": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz",
@@ -1863,6 +1872,31 @@
"integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==",
"dev": true
},
"@types/prop-types": {
"version": "15.7.3",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw=="
},
"@types/react": {
"version": "16.9.5",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.5.tgz",
"integrity": "sha512-jQ12VMiFOWYlp+j66dghOWcmDDwhca0bnlcTxS4Qz/fh5gi6wpaZDthPEu/Gc/YlAuO87vbiUXL8qKstFvuOaA==",
"requires": {
"@types/prop-types": "*",
"csstype": "^2.2.0"
}
},
"@types/react-redux": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.4.tgz",
"integrity": "sha512-SUV/7d+4L7C1Db/D4pqASgN1V1U2HnDEhEol9lYpPSguS76xFboZzf5ha2hTz6v31cUewyC7WksMh1q8JxhebQ==",
"requires": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"@types/redux-mock-store": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/redux-mock-store/-/redux-mock-store-1.0.1.tgz",
@@ -4264,6 +4298,11 @@
"cssom": "0.3.x"
}
},
"csstype": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.7.tgz",
"integrity": "sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ=="
},
"cuint": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz",
@@ -6866,6 +6905,14 @@
"minimalistic-crypto-utils": "^1.0.1"
}
},
"hoist-non-react-statics": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz",
"integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==",
"requires": {
"react-is": "^16.7.0"
}
},
"homedir-polyfill": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
@@ -7398,7 +7445,6 @@
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"dev": true,
"requires": {
"loose-envify": "^1.0.0"
}
@@ -10799,8 +10845,7 @@
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"object-copy": {
"version": "0.1.0",
@@ -11829,9 +11874,9 @@
}
},
"preact": {
"version": "8.5.2",
"resolved": "https://registry.npmjs.org/preact/-/preact-8.5.2.tgz",
"integrity": "sha512-37tlDJGq5IQKqGUbqPZ7qPtsTOWFyxe+ojAOFfzKo0dEPreenqrqgJuS83zGpeGAqD9h9L9Yr7QuxH2W4ZrKxg=="
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.0.1.tgz",
"integrity": "sha512-lq7jo1rwwCd1YkiBcuOxRc3I0y1FZACa6O7tgNXt47QZJtSlLEE53f/FDNsLtiB2IVQTHbaey20TjSPmejhDyQ=="
},
"preact-context": {
"version": "1.1.4",
@@ -11840,9 +11885,9 @@
"dev": true
},
"preact-redux": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/preact-redux/-/preact-redux-2.0.3.tgz",
"integrity": "sha1-lgpTXDImQ801mY8z8MLme8Hn6qs="
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/preact-redux/-/preact-redux-2.1.0.tgz",
"integrity": "sha512-IFjT1f5I7siBtb/jsqQbOY6QUoYHHfiHhVaxvT+C9Q++h2C7YULSC30B4gVru3Phgwgs+qWb+GUiS5EKIQb19g=="
},
"preact-render-to-string": {
"version": "4.1.0",
@@ -11972,7 +12017,6 @@
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
"dev": true,
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -12153,8 +12197,35 @@
"react-is": {
"version": "16.8.6",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
"integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==",
"dev": true
"integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA=="
},
"react-redux": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.1.tgz",
"integrity": "sha512-QsW0vcmVVdNQzEkrgzh2W3Ksvr8cqpAv5FhEk7tNEft+5pp7rXxAudTz3VOPawRkLIepItpkEIyLcN/VVXzjTg==",
"requires": {
"@babel/runtime": "^7.5.5",
"hoist-non-react-statics": "^3.3.0",
"invariant": "^2.2.4",
"loose-envify": "^1.4.0",
"prop-types": "^15.7.2",
"react-is": "^16.9.0"
},
"dependencies": {
"@babel/runtime": {
"version": "7.6.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.2.tgz",
"integrity": "sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg==",
"requires": {
"regenerator-runtime": "^0.13.2"
}
},
"react-is": {
"version": "16.10.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.10.2.tgz",
"integrity": "sha512-INBT1QEgtcCCgvccr5/86CfD71fw9EPmDxgiJX4I2Ddr6ZsV6iFXsuby+qWJPtmNuMY0zByTsG4468P7nHuNWA=="
}
}
},
"read-pkg": {
"version": "3.0.0",
@@ -12253,8 +12324,7 @@
"regenerator-runtime": {
"version": "0.13.2",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz",
"integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==",
"dev": true
"integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA=="
},
"regenerator-transform": {
"version": "0.14.1",
+4 -2
View File
@@ -94,13 +94,15 @@
},
"dependencies": {
"@github/markdown-toolbar-element": "^1.1.0",
"@types/react-redux": "^7.1.4",
"@webcomponents/custom-elements": "^1.3.0",
"bem-react-helper": "^1.1.2",
"core-js": "^3.2.1",
"focus-visible": "^5.0.2",
"intersection-observer": "^0.7.0",
"preact": "^8.5.2",
"preact-redux": "^2.0.3",
"preact": "^10.0.1",
"preact-redux": "^2.1.0",
"react-redux": "^7.1.1",
"redux": "^4.0.4",
"redux-thunk": "^2.3.0"
},
+1 -1
View File
@@ -19,7 +19,7 @@
"forceConsistentCasingInFileNames": true,
"paths": {
"*": ["@types/*"],
"@app/*": ["app/*"]
"@app/*": ["app/*"],
}
},
"include": ["app/**/*"],
+5 -1
View File
@@ -76,7 +76,11 @@ module.exports = () => ({
},
resolve: {
extensions: ['.tsx', '.ts', '.jsx', '.js'],
alias: { '@app': path.resolve(__dirname, 'app') },
alias: {
'@app': path.resolve(__dirname, 'app'),
react: 'preact/compat',
'react-dom': 'preact/compat',
},
modules: [path.resolve(__dirname, 'node_modules')],
},
module: {