From fccd6b4ca8ed7420f25f0c4298fde311bc1e09d0 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 4 Jun 2020 00:18:50 +0800 Subject: refactor(front): Make codes lint-clean! --- Timeline/ClientApp/.babelrc | 9 ++ Timeline/ClientApp/.eslintrc.js | 1 - Timeline/ClientApp/package.json | 1 + Timeline/ClientApp/src/App.tsx | 2 +- Timeline/ClientApp/src/admin/UserAdmin.tsx | 45 +++--- Timeline/ClientApp/src/common.ts | 4 + Timeline/ClientApp/src/common/AppBar.tsx | 2 +- Timeline/ClientApp/src/common/ImageCropper.tsx | 40 +++-- Timeline/ClientApp/src/common/OperationDialog.tsx | 35 ++--- Timeline/ClientApp/src/data/common.ts | 9 +- Timeline/ClientApp/src/data/timeline.ts | 5 +- Timeline/ClientApp/src/data/user.ts | 59 ++++--- Timeline/ClientApp/src/home/Home.tsx | 6 +- .../ClientApp/src/home/TimelineCreateDialog.tsx | 16 +- Timeline/ClientApp/src/i18n.ts | 11 +- Timeline/ClientApp/src/settings/Settings.tsx | 9 +- .../src/timeline/TimelineDeleteDialog.tsx | 12 +- .../ClientApp/src/timeline/TimelineInfoCard.tsx | 17 +-- Timeline/ClientApp/src/timeline/TimelineMember.tsx | 24 +-- .../src/timeline/TimelinePageTemplate.tsx | 35 +++-- .../src/timeline/TimelinePageTemplateUI.tsx | 4 +- .../ClientApp/src/timeline/TimelinePostEdit.tsx | 41 +++-- Timeline/ClientApp/src/user/ChangeAvatarDialog.tsx | 11 +- Timeline/ClientApp/src/user/Login.tsx | 3 +- Timeline/ClientApp/src/user/User.tsx | 13 +- Timeline/ClientApp/src/user/UserInfoCard.tsx | 20 +-- Timeline/ClientApp/src/user/api.ts | 5 +- Timeline/ClientApp/yarn.lock | 169 ++++++++++++++++++++- 28 files changed, 420 insertions(+), 188 deletions(-) create mode 100644 Timeline/ClientApp/src/common.ts (limited to 'Timeline/ClientApp') diff --git a/Timeline/ClientApp/.babelrc b/Timeline/ClientApp/.babelrc index 5a82470b..78d36a3d 100644 --- a/Timeline/ClientApp/.babelrc +++ b/Timeline/ClientApp/.babelrc @@ -14,6 +14,15 @@ { "decoratorsBeforeExport": true } + ], + [ + "babel-plugin-transform-builtin-extend", + { + "globals": [ + "Error", + "Array" + ] + } ] ] } \ No newline at end of file diff --git a/Timeline/ClientApp/.eslintrc.js b/Timeline/ClientApp/.eslintrc.js index 1ffb15de..73c94bf8 100644 --- a/Timeline/ClientApp/.eslintrc.js +++ b/Timeline/ClientApp/.eslintrc.js @@ -35,7 +35,6 @@ module.exports = { }, rules: { 'react/prop-types': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], '@typescript-eslint/explicit-function-return-type': [ 'warn', diff --git a/Timeline/ClientApp/package.json b/Timeline/ClientApp/package.json index d7e0b2eb..280c0a39 100644 --- a/Timeline/ClientApp/package.json +++ b/Timeline/ClientApp/package.json @@ -70,6 +70,7 @@ "@typescript-eslint/parser": "^3.0.2", "autoprefixer": "^9.8.0", "babel-loader": "^8.1.0", + "babel-plugin-transform-builtin-extend": "^1.1.2", "clean-webpack-plugin": "^3.0.0", "copy-webpack-plugin": "^6.0.1", "css-loader": "^3.5.3", diff --git a/Timeline/ClientApp/src/App.tsx b/Timeline/ClientApp/src/App.tsx index d3cfbd8a..befa2a9c 100644 --- a/Timeline/ClientApp/src/App.tsx +++ b/Timeline/ClientApp/src/App.tsx @@ -32,7 +32,7 @@ const App: React.FC = () => { const user = useOptionalUser(); React.useEffect(() => { - checkUserLoginState(); + void checkUserLoginState(); }, []); let body; diff --git a/Timeline/ClientApp/src/admin/UserAdmin.tsx b/Timeline/ClientApp/src/admin/UserAdmin.tsx index afef79c9..fc752011 100644 --- a/Timeline/ClientApp/src/admin/UserAdmin.tsx +++ b/Timeline/ClientApp/src/admin/UserAdmin.tsx @@ -18,7 +18,7 @@ import { User, UserWithToken } from '../data/user'; import { apiBaseUrl } from '../config'; async function fetchUserList(_token: string): Promise { - const res = await axios.get(`${apiBaseUrl}/users`); + const res = await axios.get(`${apiBaseUrl}/users`); return res.data; } @@ -295,7 +295,7 @@ const UserAdmin: React.FC = (props) => { | { type: 'create'; } - | { type: 'delete'; username: string } + | { type: TDelete; username: string } | { type: TChangeUsername; username: string; @@ -317,7 +317,7 @@ const UserAdmin: React.FC = (props) => { useEffect(() => { let subscribe = true; - fetchUserList(props.user.token).then((us) => { + void fetchUserList(props.user.token).then((us) => { if (subscribe) { setUsers(us); } @@ -337,7 +337,7 @@ const UserAdmin: React.FC = (props) => { close={() => setDialog(null)} process={async (user) => { const u = await createUser(user, token); - setUsers((oldUsers) => [...oldUsers!, u]); + setUsers((oldUsers) => [...(oldUsers ?? []), u]); }} /> ); @@ -351,7 +351,7 @@ const UserAdmin: React.FC = (props) => { process={async () => { await deleteUser(dialog.username, token); setUsers((oldUsers) => - oldUsers!.filter((u) => u.username !== dialog.username) + (oldUsers ?? []).filter((u) => u.username !== dialog.username) ); }} /> @@ -366,10 +366,11 @@ const UserAdmin: React.FC = (props) => { process={async (newUsername) => { await changeUsername(dialog.username, newUsername, token); setUsers((oldUsers) => { - const users = oldUsers!.slice(); - users.find( + const users = (oldUsers ?? []).slice(); + const findedUser = users.find( (u) => u.username === dialog.username - )!.username = newUsername; + ); + if (findedUser) findedUser.username = newUsername; return users; }); }} @@ -399,10 +400,11 @@ const UserAdmin: React.FC = (props) => { process={async () => { await changePermission(dialog.username, newPermission, token); setUsers((oldUsers) => { - const users = oldUsers!.slice(); - users.find( + const users = (oldUsers ?? []).slice(); + const findedUser = users.find( (u) => u.username === dialog.username - )!.administrator = newPermission; + ); + if (findedUser) findedUser.administrator = newPermission; return users; }); }} @@ -419,15 +421,18 @@ const UserAdmin: React.FC = (props) => { key={user.username} user={user} onContextMenu={(item) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const dialogInfo: any = { - type: item, - username: user.username, - }; - if (item === 'changepermission') { - dialogInfo.newPermission = !user.administrator; - } - setDialog(dialogInfo); + setDialog( + item === kChangePermission + ? { + type: kChangePermission, + username: user.username, + newPermission: !user.administrator, + } + : { + type: item, + username: user.username, + } + ); }} /> ); diff --git a/Timeline/ClientApp/src/common.ts b/Timeline/ClientApp/src/common.ts new file mode 100644 index 00000000..673f1c98 --- /dev/null +++ b/Timeline/ClientApp/src/common.ts @@ -0,0 +1,4 @@ +// This error is thrown when ui goes wrong with bad logic. +// Such as am variable should not be null, but it does. +// This error should never occur. If it does, it indicates there is some logic bug in codes. +export class UiLogicError extends Error {} diff --git a/Timeline/ClientApp/src/common/AppBar.tsx b/Timeline/ClientApp/src/common/AppBar.tsx index 39794b0f..4f61798c 100644 --- a/Timeline/ClientApp/src/common/AppBar.tsx +++ b/Timeline/ClientApp/src/common/AppBar.tsx @@ -10,7 +10,7 @@ import { useOptionalVersionedAvatarUrl } from '../user/api'; import TimelineLogo from './TimelineLogo'; -const AppBar: React.FC<{}> = (_) => { +const AppBar: React.FC = (_) => { const history = useHistory(); const user = useUser(); const avatarUrl = useOptionalVersionedAvatarUrl(user?._links?.avatar); diff --git a/Timeline/ClientApp/src/common/ImageCropper.tsx b/Timeline/ClientApp/src/common/ImageCropper.tsx index 3ef35b44..fb9bb4f9 100644 --- a/Timeline/ClientApp/src/common/ImageCropper.tsx +++ b/Timeline/ClientApp/src/common/ImageCropper.tsx @@ -1,6 +1,8 @@ import * as React from 'react'; import clsx from 'clsx'; +import { UiLogicError } from '../common'; + export interface Clip { left: number; top: number; @@ -58,11 +60,11 @@ const ImageCropper = (props: ImageCropperProps): React.ReactElement => { const c = normalizeClip(clip); - const imgElement = React.useRef(null); + const imgElementRef = React.useRef(null); const onImageRef = React.useCallback( (e: HTMLImageElement | null) => { - imgElement.current = e; + imgElementRef.current = e; if (imageElementCallback != null && e == null) { imageElementCallback(null); } @@ -123,9 +125,13 @@ const ImageCropper = (props: ImageCropperProps): React.ReactElement => { const movement = { x: e.clientX - oldState.x, y: e.clientY - oldState.y }; + const { current: imgElement } = imgElementRef; + + if (imgElement == null) throw new UiLogicError('Image element is null.'); + const moveRatio = { - x: movement.x / imgElement.current!.width, - y: movement.y / imgElement.current!.height, + x: movement.x / imgElement.width, + y: movement.y / imgElement.height, }; const newRatio = { @@ -158,9 +164,13 @@ const ImageCropper = (props: ImageCropperProps): React.ReactElement => { const ratio = imageInfo == null ? 1 : imageInfo.ratio; + const { current: imgElement } = imgElementRef; + + if (imgElement == null) throw new UiLogicError('Image element is null.'); + const moveRatio = { - x: movement.x / imgElement.current!.width, - y: movement.x / imgElement.current!.width / ratio, + x: movement.x / imgElement.width, + y: movement.x / imgElement.width / ratio, }; const newRatio = { @@ -189,6 +199,8 @@ const ImageCropper = (props: ImageCropperProps): React.ReactElement => { [imageInfo, oldState, onChange] ); + const toPercentage = (n: number): string => `${n}%`; + // fuck!!! I just can't find a better way to implement this in pure css const containerStyle: React.CSSProperties = (() => { if (imageInfo == null) { @@ -196,14 +208,14 @@ const ImageCropper = (props: ImageCropperProps): React.ReactElement => { } else { if (imageInfo.ratio > 1) { return { - width: 100 / imageInfo.ratio + '%', + width: toPercentage(100 / imageInfo.ratio), paddingTop: '100%', height: 0, }; } else { return { width: '100%', - paddingTop: 100 * imageInfo.ratio + '%', + paddingTop: toPercentage(100 * imageInfo.ratio), height: 0, }; } @@ -221,10 +233,10 @@ const ImageCropper = (props: ImageCropperProps): React.ReactElement => { className="image-cropper-mask" touch-action="none" style={{ - left: c.left * 100 + '%', - top: c.top * 100 + '%', - width: c.width * 100 + '%', - height: c.height * 100 + '%', + left: toPercentage(c.left * 100), + top: toPercentage(c.top * 100), + width: toPercentage(c.width * 100), + height: toPercentage(c.height * 100), }} onPointerMove={onPointerMove} onPointerDown={onPointerDown} @@ -235,8 +247,8 @@ const ImageCropper = (props: ImageCropperProps): React.ReactElement => { className="image-cropper-handler" touch-action="none" style={{ - left: 'calc(' + (c.left + c.width) * 100 + '% - 15px)', - top: 'calc(' + (c.top + c.height) * 100 + '% - 15px)', + left: `calc(${(c.left + c.width) * 100}% - 15px)`, + top: `calc(${(c.top + c.height) * 100}% - 15px)`, }} onPointerMove={onHandlerPointerMove} onPointerDown={onPointerDown} diff --git a/Timeline/ClientApp/src/common/OperationDialog.tsx b/Timeline/ClientApp/src/common/OperationDialog.tsx index e7b6612c..501a353e 100644 --- a/Timeline/ClientApp/src/common/OperationDialog.tsx +++ b/Timeline/ClientApp/src/common/OperationDialog.tsx @@ -1,4 +1,4 @@ -import React, { useState, InputHTMLAttributes } from 'react'; +import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Spinner, @@ -12,10 +12,12 @@ import { Button, Modal, ModalHeader, - FormText + FormText, } from 'reactstrap'; -const DefaultProcessPrompt: React.FC = _ => { +import { UiLogicError } from '../common'; + +const DefaultProcessPrompt: React.FC = (_) => { return ( @@ -27,7 +29,7 @@ interface DefaultErrorPromptProps { error?: string; } -const DefaultErrorPrompt: React.FC = props => { +const DefaultErrorPrompt: React.FC = (props) => { const { t } = useTranslation(); let result =

{t('operationDialog.error')}

; @@ -111,7 +113,7 @@ interface OperationDialogProps { onSuccessAndClose?: () => void; } -const OperationDialog: React.FC = props => { +const OperationDialog: React.FC = (props) => { const inputScheme = props.inputScheme ?? []; const { t } = useTranslation(); @@ -119,13 +121,13 @@ const OperationDialog: React.FC = props => { type Step = 'input' | 'process' | OperationResult; const [step, setStep] = useState('input'); const [values, setValues] = useState<(boolean | string)[]>( - inputScheme.map(i => { + inputScheme.map((i) => { if (i.type === 'bool') { return i.initValue ?? false; } else if (i.type === 'text' || i.type === 'select') { return i.initValue ?? ''; } else { - throw new Error('Unknown input scheme.'); + throw new UiLogicError('Unknown input scheme.'); } }) ); @@ -149,16 +151,16 @@ const OperationDialog: React.FC = props => { const onConfirm = (): void => { setStep('process'); props.onProcess(values).then( - d => { + (d: unknown) => { setStep({ type: 'success', - data: d + data: d, }); }, - e => { + (e: unknown) => { setStep({ type: 'failure', - data: e + data: e, }); } ); @@ -205,8 +207,7 @@ const OperationDialog: React.FC = props => { const newInputError: OperationInputErrorInfo = { ...oldError }; for (const [index, error] of Object.entries(newError)) { if (error !== undefined) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (newInputError as any)[index] = error; + newInputError[+index] = error as OperationInputOptionalError; } } return newInputError; @@ -236,7 +237,7 @@ const OperationDialog: React.FC = props => { {inputPrompt} {inputScheme.map((item, index) => { const value = values[index]; - const error: string | undefined = (e => + const error: string | undefined = ((e) => typeof e === 'string' ? t(e) : undefined)(inputError?.[index]); if (item.type === 'text') { @@ -246,7 +247,7 @@ const OperationDialog: React.FC = props => { { + onChange={(e) => { const v = e.target.value; const newValues = updateValue(index, v); setInputError( @@ -270,7 +271,7 @@ const OperationDialog: React.FC = props => { { + onChange={(e) => { updateValue( index, (e.target as HTMLInputElement).checked @@ -287,7 +288,7 @@ const OperationDialog: React.FC = props => { { + onChange={(event) => { updateValue(index, event.target.value); }} > diff --git a/Timeline/ClientApp/src/data/common.ts b/Timeline/ClientApp/src/data/common.ts index 61db8bd2..e2882a80 100644 --- a/Timeline/ClientApp/src/data/common.ts +++ b/Timeline/ClientApp/src/data/common.ts @@ -9,9 +9,14 @@ export function extractStatusCode(error: AxiosError): number | null { } } +export interface CommonErrorResponse { + code: number; + message: string; +} + export function extractErrorCode(error: AxiosError): number | null { - const code = - error.response && error.response.data && error.response.data.code; + const { response } = error as AxiosError; + const code = response && response.data && response.data.code; if (typeof code === 'number') { return code; } else { diff --git a/Timeline/ClientApp/src/data/timeline.ts b/Timeline/ClientApp/src/data/timeline.ts index bc5e1658..66d9e57a 100644 --- a/Timeline/ClientApp/src/data/timeline.ts +++ b/Timeline/ClientApp/src/data/timeline.ts @@ -4,6 +4,7 @@ import XRegExp from 'xregexp'; import { base64 } from './base64'; import { apiBaseUrl } from '../config'; import { User, UserAuthInfo, getCurrentUser, UserWithToken } from './user'; +import { UiLogicError } from '../common'; export const kTimelineVisibilities = ['Public', 'Register', 'Private'] as const; @@ -159,7 +160,7 @@ export class TimelineServiceTemplate< private checkUser(): UserWithToken { const user = getCurrentUser(); if (user == null) { - throw new Error('You must login to perform the operation.'); + throw new UiLogicError('You must login to perform the operation.'); } return user; } @@ -204,7 +205,7 @@ export class TimelineServiceTemplate< RawCreatePostRequestContent >((resolve) => { if (request.content.type === 'image') { - base64(request.content.data).then((d) => + void base64(request.content.data).then((d) => resolve({ ...request.content, data: d, diff --git a/Timeline/ClientApp/src/data/user.ts b/Timeline/ClientApp/src/data/user.ts index 755aecf6..b85f4f27 100644 --- a/Timeline/ClientApp/src/data/user.ts +++ b/Timeline/ClientApp/src/data/user.ts @@ -3,8 +3,10 @@ import { useState, useEffect } from 'react'; import { BehaviorSubject, Observable } from 'rxjs'; import { apiBaseUrl } from '../config'; +import { extractErrorCode } from './common'; import { pushAlert } from '../common/alert-service'; import { i18nPromise } from '../i18n'; +import { UiLogicError } from '../common'; export interface UserAuthInfo { username: string; @@ -63,30 +65,30 @@ const verifyTokenUrl = apiBaseUrl + kVerifyTokenUrl; function verifyToken(token: string): Promise { return axios .post(verifyTokenUrl, { - token: token + token: token, } as VerifyTokenRequest) - .then(res => res.data.user); + .then((res) => res.data.user); } const TOKEN_STORAGE_KEY = 'token'; export function checkUserLoginState(): Promise { if (getCurrentUser() !== undefined) - throw new Error("Already checked user. Can't check twice."); + throw new UiLogicError("Already checked user. Can't check twice."); const savedToken = window.localStorage.getItem(TOKEN_STORAGE_KEY); if (savedToken) { return verifyToken(savedToken) .then( - u => { + (u) => { const user: UserWithToken = { ...u, - token: savedToken + token: savedToken, }; - i18nPromise.then(t => { + void i18nPromise.then((t) => { pushAlert({ type: 'success', - message: t('user.welcomeBack') + message: t('user.welcomeBack'), }); }); return user; @@ -94,17 +96,17 @@ export function checkUserLoginState(): Promise { (e: AxiosError) => { if (e.response != null) { window.localStorage.removeItem(TOKEN_STORAGE_KEY); - i18nPromise.then(t => { + void i18nPromise.then((t) => { pushAlert({ type: 'danger', - message: t('user.verifyTokenFailed') + message: t('user.verifyTokenFailed'), }); }); } else { - i18nPromise.then(t => { + void i18nPromise.then((t) => { pushAlert({ type: 'danger', - message: t('user.verifyTokenFailedNetwork') + message: t('user.verifyTokenFailedNetwork'), }); }); } @@ -112,7 +114,7 @@ export function checkUserLoginState(): Promise { return null; } ) - .then(u => { + .then((u) => { userSubject.next(u); return u; }); @@ -132,18 +134,17 @@ export function userLogin( rememberMe: boolean ): Promise { if (getCurrentUser()) { - throw new Error('Already login.'); + throw new UiLogicError('Already login.'); } return axios .post(createTokenUrl, { ...credentials, expire: 30 }) - .catch(e => { - const error = e as AxiosError; - if (error.response?.data?.code === 11010101) { + .catch((e: AxiosError) => { + if (extractErrorCode(e) === 11010101) { throw new BadCredentialError(e); } throw e; }) - .then(res => { + .then((res) => { const body = res.data; const token = body.token; if (rememberMe) { @@ -151,7 +152,7 @@ export function userLogin( } const user = { ...body.user, - token + token, }; userSubject.next(user); return user; @@ -160,10 +161,10 @@ export function userLogin( export function userLogout(): void { if (getCurrentUser() === undefined) { - throw new Error('Please check user first.'); + throw new UiLogicError('Please check user first.'); } if (getCurrentUser() === null) { - throw new Error('No login.'); + throw new UiLogicError('No login.'); } window.localStorage.removeItem(TOKEN_STORAGE_KEY); userSubject.next(null); @@ -174,7 +175,7 @@ export function useOptionalUser(): UserWithToken | null | undefined { userSubject.value ); useEffect(() => { - const sub = user$.subscribe(u => setUser(u)); + const sub = user$.subscribe((u) => setUser(u)); return () => { sub.unsubscribe(); }; @@ -186,16 +187,16 @@ export function useUser(): UserWithToken | null { const [user, setUser] = useState(() => { const initUser = userSubject.value; if (initUser === undefined) { - throw new Error( + throw new UiLogicError( "This is a logic error in user module. Current user can't be undefined in useUser." ); } return initUser; }); useEffect(() => { - const sub = user$.subscribe(u => { + const sub = user$.subscribe((u) => { if (u === undefined) { - throw new Error( + throw new UiLogicError( "This is a logic error in user module. User emitted can't be undefined later." ); } @@ -208,8 +209,16 @@ export function useUser(): UserWithToken | null { return user; } +export function useUserLoggedIn(): UserWithToken { + const user = useUser(); + if (user == null) { + throw new UiLogicError('You assert user has logged in but actually not.'); + } + return user; +} + export function fetchUser(username: string): Promise { return axios .get(`${apiBaseUrl}/users/${username}`) - .then(res => res.data); + .then((res) => res.data); } diff --git a/Timeline/ClientApp/src/home/Home.tsx b/Timeline/ClientApp/src/home/Home.tsx index 25398a0a..495781d0 100644 --- a/Timeline/ClientApp/src/home/Home.tsx +++ b/Timeline/ClientApp/src/home/Home.tsx @@ -38,7 +38,7 @@ const Home: React.FC = (_) => { if (user == null) { setOwnTimelines(undefined); setJoinTimelines(undefined); - axios + void axios .get(`${apiBaseUrl}/timelines?visibility=public`) .then((res) => { if (subscribe) { @@ -47,7 +47,7 @@ const Home: React.FC = (_) => { }); } else { setPublicTimelines(undefined); - axios + void axios .get( `${apiBaseUrl}/timelines?relate=${user.username}&relateType=own` ) @@ -56,7 +56,7 @@ const Home: React.FC = (_) => { setOwnTimelines(res.data); } }); - axios + void axios .get( `${apiBaseUrl}/timelines?relate=${user.username}&relateType=join` ) diff --git a/Timeline/ClientApp/src/home/TimelineCreateDialog.tsx b/Timeline/ClientApp/src/home/TimelineCreateDialog.tsx index 30d29bc8..27c9d0d6 100644 --- a/Timeline/ClientApp/src/home/TimelineCreateDialog.tsx +++ b/Timeline/ClientApp/src/home/TimelineCreateDialog.tsx @@ -3,7 +3,7 @@ import { useHistory } from 'react-router'; import axios from 'axios'; import { apiBaseUrl } from '../config'; -import { useUser } from '../data/user'; +import { useUserLoggedIn } from '../data/user'; import { validateTimelineName } from '../data/timeline'; import OperationDialog from '../common/OperationDialog'; @@ -13,9 +13,9 @@ interface TimelineCreateDialogProps { close: () => void; } -const TimelineCreateDialog: React.FC = props => { +const TimelineCreateDialog: React.FC = (props) => { const history = useHistory(); - const user = useUser()!; + const user = useUserLoggedIn(); let nameSaved: string; @@ -30,7 +30,7 @@ const TimelineCreateDialog: React.FC = props => { type: 'text', label: 'home.createDialog.name', helperText: 'home.createDialog.nameFormat', - validator: name => { + validator: (name) => { if (name.length === 0) { return 'home.createDialog.noEmpty'; } else if (name.length > 26) { @@ -40,19 +40,19 @@ const TimelineCreateDialog: React.FC = props => { } else { return null; } - } - } + }, + }, ]} onProcess={([name]) => { nameSaved = name as string; return axios.post(`${apiBaseUrl}/timelines?token=${user.token}`, { - name + name, }); }} onSuccessAndClose={() => { history.push(`timelines/${nameSaved}`); }} - failurePrompt={e => (e as object).toString()} + failurePrompt={(e) => `${e as string}`} /> ); }; diff --git a/Timeline/ClientApp/src/i18n.ts b/Timeline/ClientApp/src/i18n.ts index 9f3a0801..a752cfa4 100644 --- a/Timeline/ClientApp/src/i18n.ts +++ b/Timeline/ClientApp/src/i18n.ts @@ -6,8 +6,7 @@ const backend: BackendModule = { type: 'backend', async read(language, namespace, callback) { function error(message: string): void { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - callback(new Error(message), false as any); + callback(new Error(message), false); } function success(result: ResourceKey): void { @@ -37,7 +36,7 @@ const backend: BackendModule = { } }, init() {}, // eslint-disable-line @typescript-eslint/no-empty-function - create() {} // eslint-disable-line @typescript-eslint/no-empty-function + create() {}, // eslint-disable-line @typescript-eslint/no-empty-function }; export const i18nPromise = i18n @@ -51,8 +50,8 @@ export const i18nPromise = i18n debug: process.env.NODE_ENV === 'development', interpolation: { - escapeValue: false // not needed for react!! - } + escapeValue: false, // not needed for react!! + }, // react i18next special options (optional) // override if needed - omit if ok with defaults @@ -72,7 +71,7 @@ if (module.hot) { module.hot.accept( ['./locales/en/translation', './locales/zh/translation'], () => { - i18n.reloadResources(); + void i18n.reloadResources(); } ); } diff --git a/Timeline/ClientApp/src/settings/Settings.tsx b/Timeline/ClientApp/src/settings/Settings.tsx index b3996fdc..075f8699 100644 --- a/Timeline/ClientApp/src/settings/Settings.tsx +++ b/Timeline/ClientApp/src/settings/Settings.tsx @@ -6,12 +6,13 @@ import { Container, Row, Col, Input } from 'reactstrap'; import { apiBaseUrl } from '../config'; -import { useUser, userLogout } from '../data/user'; +import { useUser, userLogout, useUserLoggedIn } from '../data/user'; import AppBar from '../common/AppBar'; import OperationDialog, { OperationInputErrorInfo, } from '../common/OperationDialog'; +import { CommonErrorResponse } from '../data/common'; interface ChangePasswordDialogProps { open: boolean; @@ -30,7 +31,7 @@ async function changePassword( newPassword, }); } catch (e) { - const error = e as AxiosError; + const error = e as AxiosError; if ( error.response && error.response.status === 400 && @@ -44,7 +45,7 @@ async function changePassword( } const ChangePasswordDialog: React.FC = (props) => { - const user = useUser()!; + const user = useUserLoggedIn(); const history = useHistory(); const { t } = useTranslation(); @@ -176,7 +177,7 @@ const Settings: React.FC = (_) => { type="select" value={language} onChange={(e) => { - i18n.changeLanguage(e.target.value); + void i18n.changeLanguage(e.target.value); }} > diff --git a/Timeline/ClientApp/src/timeline/TimelineDeleteDialog.tsx b/Timeline/ClientApp/src/timeline/TimelineDeleteDialog.tsx index 2b682a6b..e2d8ad5a 100644 --- a/Timeline/ClientApp/src/timeline/TimelineDeleteDialog.tsx +++ b/Timeline/ClientApp/src/timeline/TimelineDeleteDialog.tsx @@ -4,7 +4,7 @@ import { useHistory } from 'react-router'; import { Trans } from 'react-i18next'; import { apiBaseUrl } from '../config'; -import { useUser } from '../data/user'; +import { useUserLoggedIn } from '../data/user'; import OperationDialog from '../common/OperationDialog'; interface TimelineDeleteDialog { @@ -13,8 +13,8 @@ interface TimelineDeleteDialog { close: () => void; } -const TimelineDeleteDialog: React.FC = props => { - const user = useUser()!; +const TimelineDeleteDialog: React.FC = (props) => { + const user = useUserLoggedIn(); const history = useHistory(); const { name } = props; @@ -35,14 +35,14 @@ const TimelineDeleteDialog: React.FC = props => { inputScheme={[ { type: 'text', - validator: value => { + validator: (value) => { if (value !== name) { return 'timeline.deleteDialog.notMatch'; } else { return null; } - } - } + }, + }, ]} onProcess={() => { return axios.delete( diff --git a/Timeline/ClientApp/src/timeline/TimelineInfoCard.tsx b/Timeline/ClientApp/src/timeline/TimelineInfoCard.tsx index 2ce7c378..b3af4cab 100644 --- a/Timeline/ClientApp/src/timeline/TimelineInfoCard.tsx +++ b/Timeline/ClientApp/src/timeline/TimelineInfoCard.tsx @@ -28,6 +28,7 @@ const TimelineInfoCard: React.FC = (props) => { const { t } = useTranslation(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const containerRef = React.useRef(null!); const notifyHeight = React.useCallback((): void => { @@ -48,13 +49,6 @@ const TimelineInfoCard: React.FC = (props) => { (): void => setManageDropdownOpen((old) => !old), [] ); - const onManageProperty = React.useCallback( - (): void => onManage!('property'), - [onManage] - ); - const onManageDelete = React.useCallback((): void => onManage!('delete'), [ - onManage, - ]); return (
= (props) => { {t(timelineVisibilityTooltipTranslationMap[props.timeline.visibility])}
- {props.onManage != null ? ( + {onManage != null ? ( {t('timeline.manage')} - + onManage('property')}> {t('timeline.manageItem.property')} {t('timeline.manageItem.member')} - + onManage('delete')} + > {t('timeline.manageItem.delete')} diff --git a/Timeline/ClientApp/src/timeline/TimelineMember.tsx b/Timeline/ClientApp/src/timeline/TimelineMember.tsx index eac8d417..b8bb49ee 100644 --- a/Timeline/ClientApp/src/timeline/TimelineMember.tsx +++ b/Timeline/ClientApp/src/timeline/TimelineMember.tsx @@ -11,7 +11,7 @@ import { Modal, Row, Col, - Button + Button, } from 'reactstrap'; export interface TimelineMemberCallbacks { @@ -25,7 +25,7 @@ export interface TimelineMemberProps { edit: TimelineMemberCallbacks | null | undefined; } -const TimelineMember: React.FC = props => { +const TimelineMember: React.FC = (props) => { const { t } = useTranslation(); const [userSearchText, setUserSearchText] = useState(''); @@ -87,7 +87,7 @@ const TimelineMember: React.FC = props => { <> { + onChange={(v) => { setUserSearchText(v); }} loading={userSearchState.type === 'loading'} @@ -95,27 +95,27 @@ const TimelineMember: React.FC = props => { if (userSearchText === '') { setUserSearchState({ type: 'error', - data: 'login.emptyUsername' + data: 'login.emptyUsername', }); return; } setUserSearchState({ type: 'loading' }); edit.onCheckUser(userSearchText).then( - u => { + (u) => { if (u == null) { setUserSearchState({ type: 'error', - data: 'timeline.userNotExist' + data: 'timeline.userNotExist', }); } else { setUserSearchState({ type: 'user', data: u }); } }, - e => { + (e) => { setUserSearchState({ type: 'error', - data: e.toString() + data: `${e as string}`, }); } ); @@ -125,7 +125,7 @@ const TimelineMember: React.FC = props => { if (userSearchState.type === 'user') { const u = userSearchState.data; const addable = - members.findIndex(m => m.username === u.username) === -1; + members.findIndex((m) => m.username === u.username) === -1; return ( <> {!addable ? ( @@ -150,7 +150,7 @@ const TimelineMember: React.FC = props => { className="align-self-center" disabled={!addable} onClick={() => { - edit.onAddUser(u).then(_ => { + void edit.onAddUser(u).then((_) => { setUserSearchText(''); setUserSearchState({ type: 'init' }); }); @@ -185,7 +185,9 @@ export interface TimelineMemberDialogProps extends TimelineMemberProps { onClose: () => void; } -export const TimelineMemberDialog: React.FC = props => { +export const TimelineMemberDialog: React.FC = ( + props +) => { return ( diff --git a/Timeline/ClientApp/src/timeline/TimelinePageTemplate.tsx b/Timeline/ClientApp/src/timeline/TimelinePageTemplate.tsx index 3660ad78..2cfb4341 100644 --- a/Timeline/ClientApp/src/timeline/TimelinePageTemplate.tsx +++ b/Timeline/ClientApp/src/timeline/TimelinePageTemplate.tsx @@ -19,6 +19,7 @@ import { TimelineMemberDialog } from './TimelineMember'; import TimelinePropertyChangeDialog from './TimelinePropertyChangeDialog'; import { TimelinePageTemplateUIProps } from './TimelinePageTemplateUI'; import { TimelinePostSendCallback } from './TimelinePostEdit'; +import { UiLogicError } from '../common'; export interface TimelinePageTemplateProps< TManageItem, @@ -88,7 +89,7 @@ export default function TimelinePageTemplate< }, (error) => { if (subscribe) { - setError(error.toString()); + setError(`${error as string}`); } } ); @@ -129,13 +130,19 @@ export default function TimelinePageTemplate< let dialogElement: React.ReactElement | undefined; if (dialog === 'property') { + if (timeline == null) { + throw new UiLogicError( + 'Timeline is null but attempt to open change property dialog.' + ); + } + dialogElement = ( { return service.changeProperty(name, req).then((newTimeline) => { @@ -145,13 +152,19 @@ export default function TimelinePageTemplate< /> ); } else if (dialog === 'member') { + if (timeline == null) { + throw new UiLogicError( + 'Timeline is null but attempt to open change property dialog.' + ); + } + dialogElement = ( { return fetchUser(u).catch((e) => { @@ -168,18 +181,18 @@ export default function TimelinePageTemplate< onAddUser: (u) => { return service.addMember(name, u.username).then((_) => { setTimeline({ - ...timeline!, - members: concat(timeline!.members, u), + ...timeline, + members: concat(timeline.members, u), }); }); }, onRemoveUser: (u) => { - service.removeMember(name, u).then((_) => { + void service.removeMember(name, u).then((_) => { setTimeline({ - ...timeline!, + ...timeline, members: without( - timeline!.members, - timeline!.members.find((m) => m.username === u) + timeline.members, + timeline.members.find((m) => m.username === u) ), }); }); diff --git a/Timeline/ClientApp/src/timeline/TimelinePageTemplateUI.tsx b/Timeline/ClientApp/src/timeline/TimelinePageTemplateUI.tsx index d96b3260..22fb1987 100644 --- a/Timeline/ClientApp/src/timeline/TimelinePageTemplateUI.tsx +++ b/Timeline/ClientApp/src/timeline/TimelinePageTemplateUI.tsx @@ -44,7 +44,7 @@ export default function TimelinePageTemplateUI( const onPostEditHeightChange = React.useCallback((height: number): void => { const { current: bottomSpaceDiv } = bottomSpaceRef; if (bottomSpaceDiv != null) { - bottomSpaceDiv.style.height = height + 'px'; + bottomSpaceDiv.style.height = `${height}px`; } if (height === 0) { const alertHost = getAlertHost(); @@ -54,7 +54,7 @@ export default function TimelinePageTemplateUI( } else { const alertHost = getAlertHost(); if (alertHost != null) { - alertHost.style.marginBottom = height + 'px'; + alertHost.style.marginBottom = `${height}px`; } } }, []); diff --git a/Timeline/ClientApp/src/timeline/TimelinePostEdit.tsx b/Timeline/ClientApp/src/timeline/TimelinePostEdit.tsx index fe1fda9b..2b76b03b 100644 --- a/Timeline/ClientApp/src/timeline/TimelinePostEdit.tsx +++ b/Timeline/ClientApp/src/timeline/TimelinePostEdit.tsx @@ -1,12 +1,13 @@ import React from 'react'; import clsx from 'clsx'; -import { Container, Button, Spinner, Row, Col } from 'reactstrap'; +import { Button, Spinner, Row, Col } from 'reactstrap'; import { useTranslation } from 'react-i18next'; import { pushAlert } from '../common/alert-service'; import { CreatePostRequest } from '../data/timeline'; import FileInput from '../common/FileInput'; +import { UiLogicError } from '../common'; interface TimelinePostEditImageProps { onSelect: (blob: Blob | null) => void; @@ -96,11 +97,12 @@ const TimelinePostEdit: React.FC = (props) => { const canSend = kind === 'text' || (kind === 'image' && imageBlob != null); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const containerRef = React.useRef(null!); + React.useEffect(() => { if (props.onHeightChange) { - props.onHeightChange( - document.getElementById('timeline-post-edit-area')!.clientHeight - ); + props.onHeightChange(containerRef.current.clientHeight); } return () => { if (props.onHeightChange) { @@ -117,20 +119,31 @@ const TimelinePostEdit: React.FC = (props) => { const onSend = React.useCallback(() => { setState('process'); - const req: CreatePostRequest = - kind === 'text' - ? { + const req: CreatePostRequest = (() => { + switch (kind) { + case 'text': + return { content: { type: 'text', text: text, }, + } as CreatePostRequest; + case 'image': + if (imageBlob == null) { + throw new UiLogicError( + 'Content type is image but image blob is null.' + ); } - : { + return { content: { type: 'image', - data: imageBlob!, + data: imageBlob, }, - }; + } as CreatePostRequest; + default: + throw new UiLogicError('Unknown content type.'); + } + })(); onPost(req).then( (_) => { @@ -155,11 +168,7 @@ const TimelinePostEdit: React.FC = (props) => { }, []); return ( - +
{kind === 'text' ? ( @@ -198,7 +207,7 @@ const TimelinePostEdit: React.FC = (props) => { })()} - +
); }; diff --git a/Timeline/ClientApp/src/user/ChangeAvatarDialog.tsx b/Timeline/ClientApp/src/user/ChangeAvatarDialog.tsx index e082d5a0..f7b25252 100644 --- a/Timeline/ClientApp/src/user/ChangeAvatarDialog.tsx +++ b/Timeline/ClientApp/src/user/ChangeAvatarDialog.tsx @@ -11,6 +11,7 @@ import { import { AxiosError } from 'axios'; import ImageCropper, { Clip, applyClipToImage } from '../common/ImageCropper'; +import { UiLogicError } from '../common'; export interface ChangeAvatarDialogProps { open: boolean; @@ -107,11 +108,11 @@ const ChangeAvatarDialog: React.FC = (props) => { clip.width === 0 || file == null ) { - throw new Error('Invalid state.'); + throw new UiLogicError(); } setState('processcrop'); - applyClipToImage(cropImgElement, clip, file.type).then((b) => { + void applyClipToImage(cropImgElement, clip, file.type).then((b) => { setResultBlob(b); }); }, [cropImgElement, clip, file]); @@ -130,7 +131,7 @@ const ChangeAvatarDialog: React.FC = (props) => { const upload = React.useCallback(() => { if (resultBlob == null) { - throw new Error('Invalid state.'); + throw new UiLogicError(); } setState('uploading'); @@ -147,7 +148,7 @@ const ChangeAvatarDialog: React.FC = (props) => { const createPreviewRow = (): React.ReactElement => { if (resultUrl == null) { - throw new Error('Invalid state.'); + throw new UiLogicError(); } return ( @@ -182,7 +183,7 @@ const ChangeAvatarDialog: React.FC = (props) => { ); } else if (state === 'crop') { if (fileUrl == null) { - throw new Error('Invalid state.'); + throw new UiLogicError(); } return ( <> diff --git a/Timeline/ClientApp/src/user/Login.tsx b/Timeline/ClientApp/src/user/Login.tsx index fc9364c0..24cd58d0 100644 --- a/Timeline/ClientApp/src/user/Login.tsx +++ b/Timeline/ClientApp/src/user/Login.tsx @@ -1,6 +1,7 @@ import React, { Fragment, useState, useEffect } from 'react'; import { useHistory } from 'react-router'; import { useTranslation } from 'react-i18next'; +import { AxiosError } from 'axios'; import AppBar from '../common/AppBar'; @@ -68,7 +69,7 @@ const Login: React.FC = (_) => { history.goBack(); } }, - (e) => { + (e: AxiosError | Error) => { setProcess(false); setError(e.message); } diff --git a/Timeline/ClientApp/src/user/User.tsx b/Timeline/ClientApp/src/user/User.tsx index 7bdd64b7..a281be42 100644 --- a/Timeline/ClientApp/src/user/User.tsx +++ b/Timeline/ClientApp/src/user/User.tsx @@ -10,6 +10,7 @@ import ChangeNicknameDialog from './ChangeNicknameDialog'; import ChangeAvatarDialog from './ChangeAvatarDialog'; import TimelinePageTemplate from '../timeline/TimelinePageTemplate'; import { PersonalTimelineManageItem } from './UserInfoCard'; +import { UiLogicError } from '../common'; const User: React.FC = (_) => { const { username } = useParams<{ username: string }>(); @@ -26,12 +27,16 @@ const User: React.FC = (_) => { }; if (dialog === 'nickname') { + if (user == null) { + throw new UiLogicError('Change nickname without login.'); + } + dialogElement = ( { - const p = changeNickname(user!.token, username, newNickname); + const p = changeNickname(user.token, username, newNickname); return p.then((_) => { setDataKey(dataKey + 1); }); @@ -39,11 +44,15 @@ const User: React.FC = (_) => { /> ); } else if (dialog === 'avatar') { + if (user == null) { + throw new UiLogicError('Change avatar without login.'); + } + dialogElement = ( changeAvatar(user!.token, username, file, file.type)} + process={(file) => changeAvatar(user.token, username, file, file.type)} /> ); } diff --git a/Timeline/ClientApp/src/user/UserInfoCard.tsx b/Timeline/ClientApp/src/user/UserInfoCard.tsx index 280cddce..3f812a8b 100644 --- a/Timeline/ClientApp/src/user/UserInfoCard.tsx +++ b/Timeline/ClientApp/src/user/UserInfoCard.tsx @@ -39,6 +39,7 @@ const UserInfoCard: React.FC = (props) => { props.timeline.owner._links.avatar ); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const containerRef = React.useRef(null!); const notifyHeight = React.useCallback((): void => { @@ -59,17 +60,6 @@ const UserInfoCard: React.FC = (props) => { (): void => setManageDropdownOpen((old) => !old), [] ); - const onManageProperty = React.useCallback( - (): void => onManage!('property'), - [onManage] - ); - const onManageAvatar = React.useCallback((): void => onManage!('avatar'), [ - onManage, - ]); - const onManageNickname = React.useCallback( - (): void => onManage!('nickname'), - [onManage] - ); return (
= (props) => { {t(timelineVisibilityTooltipTranslationMap[props.timeline.visibility])}
- {props.onManage != null ? ( + {onManage != null ? ( {t('timeline.manage')} - + onManage('nickname')}> {t('timeline.manageItem.nickname')} - + onManage('avatar')}> {t('timeline.manageItem.avatar')} - + onManage('property')}> {t('timeline.manageItem.property')} diff --git a/Timeline/ClientApp/src/user/api.ts b/Timeline/ClientApp/src/user/api.ts index 184c011f..583370e2 100644 --- a/Timeline/ClientApp/src/user/api.ts +++ b/Timeline/ClientApp/src/user/api.ts @@ -60,7 +60,7 @@ export function useOptionalVersionedAvatarUrl( ? undefined : updateQueryString( 'v', - avatarVersion == null ? null : avatarVersion + '', + avatarVersion == null ? null : avatarVersion.toString(), url ), [avatarVersion, url] @@ -72,7 +72,8 @@ export function useAvatarUrlWithGivenVersion( url: string ): string { return React.useMemo( - () => updateQueryString('v', version == null ? null : version + '', url), + () => + updateQueryString('v', version == null ? null : version.toString(), url), [version, url] ); } diff --git a/Timeline/ClientApp/yarn.lock b/Timeline/ClientApp/yarn.lock index fa6cd303..5bde52d8 100644 --- a/Timeline/ClientApp/yarn.lock +++ b/Timeline/ClientApp/yarn.lock @@ -2005,6 +2005,13 @@ __metadata: languageName: node linkType: hard +"ansi-styles@npm:^2.2.1": + version: 2.2.1 + resolution: "ansi-styles@npm:2.2.1" + checksum: 3/108c7496372982f1ee53f3f09975de89cc771d2f7c89a32d56ab7a542f67b7de97391c9c16b43b39eb7ea176d3cfbb15975b6b355ae827f15f5a457b1b9dec31 + languageName: node + linkType: hard + "ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1": version: 3.2.1 resolution: "ansi-styles@npm:3.2.1" @@ -2276,6 +2283,17 @@ __metadata: languageName: node linkType: hard +"babel-code-frame@npm:^6.26.0": + version: 6.26.0 + resolution: "babel-code-frame@npm:6.26.0" + dependencies: + chalk: ^1.1.3 + esutils: ^2.0.2 + js-tokens: ^3.0.2 + checksum: 3/cc2a799ccc341ad2db8aa90762b680bbca1e15893c3b28a328e974f46443110b8c56bad25554efe26f8955d19cfa2955b5f3068310ab8a818a9d7e875c90e8b4 + languageName: node + linkType: hard + "babel-loader@npm:^8.1.0": version: 8.1.0 resolution: "babel-loader@npm:8.1.0" @@ -2292,6 +2310,15 @@ __metadata: languageName: node linkType: hard +"babel-messages@npm:^6.23.0": + version: 6.23.0 + resolution: "babel-messages@npm:6.23.0" + dependencies: + babel-runtime: ^6.22.0 + checksum: 3/9a04e1b86bf68f0469efc7d09d04dbcd69d386ae0347d71c67b8867d6763f0b46616856ca3669b982e9e000642b0546271ecda51c1e1a321fc8cf179ad6d65ad + languageName: node + linkType: hard + "babel-plugin-dynamic-import-node@npm:^2.3.3": version: 2.3.3 resolution: "babel-plugin-dynamic-import-node@npm:2.3.3" @@ -2301,6 +2328,77 @@ __metadata: languageName: node linkType: hard +"babel-plugin-transform-builtin-extend@npm:^1.1.2": + version: 1.1.2 + resolution: "babel-plugin-transform-builtin-extend@npm:1.1.2" + dependencies: + babel-runtime: ^6.2.0 + babel-template: ^6.3.0 + checksum: 3/29040a2217234d99930485c0a7e20d93bcd26dc8f7d51e0824f982d0f93d1147042cf89ad78f4502cd5afc2d036fbadae6615f79ef70fd6c12fe9c235671e5f3 + languageName: node + linkType: hard + +"babel-runtime@npm:^6.2.0, babel-runtime@npm:^6.22.0, babel-runtime@npm:^6.26.0": + version: 6.26.0 + resolution: "babel-runtime@npm:6.26.0" + dependencies: + core-js: ^2.4.0 + regenerator-runtime: ^0.11.0 + checksum: 3/5010bf1d81e484d9c6a5b4e4c32564a0dc180c2dc5a65f999729c3cb63b9c6e805d3d10c19a4ccc2112bce084e39e51e52daf5c21df0141ce8e6e37727af2e0b + languageName: node + linkType: hard + +"babel-template@npm:^6.3.0": + version: 6.26.0 + resolution: "babel-template@npm:6.26.0" + dependencies: + babel-runtime: ^6.26.0 + babel-traverse: ^6.26.0 + babel-types: ^6.26.0 + babylon: ^6.18.0 + lodash: ^4.17.4 + checksum: 3/3c76998a19c90e1aa2c37c4fd6a83b01db685df169f83669a40c459deda30792c40de1f2a1ffe405c9c8c25e4cd00733c0185244f5c457e94579bbe272bb22ef + languageName: node + linkType: hard + +"babel-traverse@npm:^6.26.0": + version: 6.26.0 + resolution: "babel-traverse@npm:6.26.0" + dependencies: + babel-code-frame: ^6.26.0 + babel-messages: ^6.23.0 + babel-runtime: ^6.26.0 + babel-types: ^6.26.0 + babylon: ^6.18.0 + debug: ^2.6.8 + globals: ^9.18.0 + invariant: ^2.2.2 + lodash: ^4.17.4 + checksum: 3/05030a5152139565a2fe1d277d6ff337df33fe406b9eedb04c8fb740d2c26008d051457437fbf32faace5ea3cee968dcfba0bc4f359743917a03d1ee945ca5f3 + languageName: node + linkType: hard + +"babel-types@npm:^6.26.0": + version: 6.26.0 + resolution: "babel-types@npm:6.26.0" + dependencies: + babel-runtime: ^6.26.0 + esutils: ^2.0.2 + lodash: ^4.17.4 + to-fast-properties: ^1.0.3 + checksum: 3/9fb4b712afe5c19bfd2ab77e9888062d1b4888be22d8b21934fee03dbbd6add8d89ce9e435b7321a1047776c14f713301666112a5c5e505cb673e88d63076cca + languageName: node + linkType: hard + +"babylon@npm:^6.18.0": + version: 6.18.0 + resolution: "babylon@npm:6.18.0" + bin: + babylon: ./bin/babylon.js + checksum: 3/af38302e3fd8b01004ab03e7f42e00d3d6b3e85190102a1ad7ffbed87bc025d96413a7c165b2b5f0b78e576b71e5306a67c1ae0368f6d12bef40fd00b0dbc7b5 + languageName: node + linkType: hard + "balanced-match@npm:^1.0.0": version: 1.0.0 resolution: "balanced-match@npm:1.0.0" @@ -2774,6 +2872,19 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^1.1.3": + version: 1.1.3 + resolution: "chalk@npm:1.1.3" + dependencies: + ansi-styles: ^2.2.1 + escape-string-regexp: ^1.0.2 + has-ansi: ^2.0.0 + strip-ansi: ^3.0.0 + supports-color: ^2.0.0 + checksum: 3/bc2df54f6da63d0918254aa2d79dd87f75442e35c751b07f5ca37e5886dd0963472e37ee8c5fa6da27710fdfa0e10779c72be4a6c860c67e96769ba63ee2901e + languageName: node + linkType: hard + "chalk@npm:^4.0.0": version: 4.0.0 resolution: "chalk@npm:4.0.0" @@ -3227,6 +3338,13 @@ __metadata: languageName: node linkType: hard +"core-js@npm:^2.4.0": + version: 2.6.11 + resolution: "core-js@npm:2.6.11" + checksum: 3/39ad00b46deaecf344470ef940949b58c4b15e71608a98e8ac7429cd16e485b829f9720d44a44b5f4dee966ff04a18baf5283feb8aaba2cb59ce2c25fa72b88c + languageName: node + linkType: hard + "core-js@npm:^3.6.5": version: 3.6.5 resolution: "core-js@npm:3.6.5" @@ -3434,7 +3552,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.3.3": +"debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.3.3, debug@npm:^2.6.8": version: 2.6.9 resolution: "debug@npm:2.6.9" dependencies: @@ -3993,7 +4111,7 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:^1.0.5": +"escape-string-regexp@npm:^1.0.2, escape-string-regexp@npm:^1.0.5": version: 1.0.5 resolution: "escape-string-regexp@npm:1.0.5" checksum: 3/f9484b8b4c8827d816e0fd905c25ed4b561376a9c220e1430403ea84619bf680c76a883a48cff8b8e091daf55d6a497e37479f9787b9f15f3c421b6054289744 @@ -4972,6 +5090,13 @@ fsevents@~2.1.2: languageName: node linkType: hard +"globals@npm:^9.18.0": + version: 9.18.0 + resolution: "globals@npm:9.18.0" + checksum: 3/ad86a4b4d7348a259c4a0748c5f4c7c1c51c624567a48538092b7fc0b6cba7d9de9d16e7b788c693b590856608f9a697af2295ff278ff442d8fa705dd1240164 + languageName: node + linkType: hard + "globby@npm:^11.0.0": version: 11.0.0 resolution: "globby@npm:11.0.0" @@ -5037,6 +5162,15 @@ fsevents@~2.1.2: languageName: node linkType: hard +"has-ansi@npm:^2.0.0": + version: 2.0.0 + resolution: "has-ansi@npm:2.0.0" + dependencies: + ansi-regex: ^2.0.0 + checksum: 3/c6805f5d01ced45ba247ff2b8c914f401e70aa9086552d8eafbdf6bc0b0e38ea4a3bf1a387d100ff5f07e5854bca96532a01777820a16be2cdf8cf6582091bad + languageName: node + linkType: hard + "has-flag@npm:^3.0.0": version: 3.0.0 resolution: "has-flag@npm:3.0.0" @@ -6016,6 +6150,13 @@ fsevents@~2.1.2: languageName: node linkType: hard +"js-tokens@npm:^3.0.2": + version: 3.0.2 + resolution: "js-tokens@npm:3.0.2" + checksum: 3/81e634d5a909ba8294758ddca47a0c0cfee90e84cc14973c072a3a27456b387ed8def0f24aff7074c02c3ba47531f4ad8b58320f5f5c4216ef46257de5350568 + languageName: node + linkType: hard + "js-yaml@npm:^3.13.1": version: 3.13.1 resolution: "js-yaml@npm:3.13.1" @@ -6303,7 +6444,7 @@ fsevents@~2.1.2: languageName: node linkType: hard -"lodash@npm:^4.17.11, lodash@npm:^4.17.13, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.3": +"lodash@npm:^4.17.11, lodash@npm:^4.17.13, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.3, lodash@npm:^4.17.4": version: 4.17.15 resolution: "lodash@npm:4.17.15" checksum: 3/aec3fbb7570aa67bda500b8299b1b1821d60646bede87f76a74dfcc7666ab3445267d734ec71424d70809d52ad67a1356fab5ab694a3faa1908d68e9d48f00f5 @@ -8325,6 +8466,13 @@ fsevents@~2.1.2: languageName: node linkType: hard +"regenerator-runtime@npm:^0.11.0": + version: 0.11.1 + resolution: "regenerator-runtime@npm:0.11.1" + checksum: 3/d98d44b9f5c9c3c670dcb615c5f5374931f937f3075dc8338126f45231643aa8c47ed2bfdef6ae593e311be54ca02d25d943971ca86a3dc1fa99068c2e1b88b2 + languageName: node + linkType: hard + "regenerator-runtime@npm:^0.13.2, regenerator-runtime@npm:^0.13.4, regenerator-runtime@npm:^0.13.5": version: 0.13.5 resolution: "regenerator-runtime@npm:0.13.5" @@ -9477,6 +9625,13 @@ fsevents@~2.1.2: languageName: node linkType: hard +"supports-color@npm:^2.0.0": + version: 2.0.0 + resolution: "supports-color@npm:2.0.0" + checksum: 3/5d6fb449e29f779cc639756f0d6b9ab6138048e753683cd2c647f36a9254714051909a5f569e6aa83c5310c8dfe8a1f481967e02bef401ac8eed46ee0950d779 + languageName: node + linkType: hard + "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -9637,6 +9792,7 @@ fsevents@~2.1.2: autoprefixer: ^9.8.0 axios: ^0.19.2 babel-loader: ^8.1.0 + babel-plugin-transform-builtin-extend: ^1.1.2 bootstrap: ^4.5.0 classnames: ^2.2.6 clean-webpack-plugin: ^3.0.0 @@ -9722,6 +9878,13 @@ fsevents@~2.1.2: languageName: node linkType: hard +"to-fast-properties@npm:^1.0.3": + version: 1.0.3 + resolution: "to-fast-properties@npm:1.0.3" + checksum: 3/bfe41c936705c9d82ed21ac6f39384c323d860f74b55d2354bc511eeb5f5729849f70ea4c1fab8ad71ca5717fa184c94899778e4959ef09e7316708086cf04c6 + languageName: node + linkType: hard + "to-fast-properties@npm:^2.0.0": version: 2.0.0 resolution: "to-fast-properties@npm:2.0.0" -- cgit v1.2.3