1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-24 20:36:40 +07:00

add eslint-plugin-prettier

This commit is contained in:
Fedor Katurov 2025-01-24 17:51:59 +07:00
parent 0e4d2bf44d
commit ba0604ab9d
69 changed files with 419 additions and 249 deletions

View file

@ -1,6 +1,7 @@
module.exports = { module.exports = {
extends: ['plugin:react/recommended', 'plugin:@next/next/recommended'], extends: ['plugin:react/recommended', 'plugin:@next/next/recommended'],
rules: { rules: {
'prettier/prettier': 'error',
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks 'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies 'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies
'react/prop-types': 0, 'react/prop-types': 0,
@ -9,13 +10,21 @@ module.exports = {
'@next/next/no-img-element': 0, '@next/next/no-img-element': 0,
'unused-imports/no-unused-imports': 'warn', 'unused-imports/no-unused-imports': 'warn',
// 'no-unused-vars': 'warn', // 'no-unused-vars': 'warn',
'quotes': [2, 'single', { 'avoidEscape': true }], quotes: [2, 'single', { avoidEscape: true }],
'import/order': [ 'import/order': [
'error', 'error',
{ {
alphabetize: { order: 'asc' }, alphabetize: { order: 'asc' },
'newlines-between': 'always', 'newlines-between': 'always',
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'unknown'], groups: [
'builtin',
'external',
'internal',
'parent',
'sibling',
'index',
'unknown',
],
pathGroups: [ pathGroups: [
{ {
pattern: 'react', pattern: 'react',
@ -34,18 +43,17 @@ module.exports = {
paths: [ paths: [
{ {
name: 'ramda', name: 'ramda',
message: message: "import from '~/utils/ramda' instead",
'import from \'~/utils/ramda\' instead',
}, },
], ],
}, },
] ],
}, },
parserOptions: { parserOptions: {
ecmaVersion: 7, ecmaVersion: 7,
sourceType: 'module', sourceType: 'module',
}, },
plugins: ['import', 'react-hooks', 'unused-imports'], plugins: ['import', 'react-hooks', 'unused-imports', 'prettier'],
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
settings: { settings: {
react: { react: {

View file

@ -92,13 +92,14 @@
"@typescript-eslint/parser": "^5.10.1", "@typescript-eslint/parser": "^5.10.1",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-plugin-import": "^2.25.4", "eslint-plugin-import": "^2.25.4",
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-react": "^7.28.0", "eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-unused-imports": "^3.0.0", "eslint-plugin-unused-imports": "^3.0.0",
"husky": "^7.0.4", "husky": "^7.0.4",
"lint-staged": "^12.1.6", "lint-staged": "^12.1.6",
"next-transpile-modules": "^9.0.0", "next-transpile-modules": "^9.0.0",
"prettier": "^2.7.1" "prettier": "^3.0.0"
}, },
"lint-staged": { "lint-staged": {
"./**/*.{js,jsx,ts,tsx}": [ "./**/*.{js,jsx,ts,tsx}": [

View file

@ -14,7 +14,7 @@ interface Props extends DivProps {
username?: string; username?: string;
size?: number; size?: number;
hasUpdates?: boolean; hasUpdates?: boolean;
preset?: typeof imagePresets[keyof typeof imagePresets]; preset?: (typeof imagePresets)[keyof typeof imagePresets];
} }
const Avatar = forwardRef<HTMLDivElement, Props>( const Avatar = forwardRef<HTMLDivElement, Props>(

View file

@ -20,7 +20,7 @@ export const COMMENT_BLOCK_DETECTORS = [
]; ];
export type ICommentBlock = { export type ICommentBlock = {
type: typeof COMMENT_BLOCK_TYPES[keyof typeof COMMENT_BLOCK_TYPES]; type: (typeof COMMENT_BLOCK_TYPES)[keyof typeof COMMENT_BLOCK_TYPES];
content: string; content: string;
}; };

View file

@ -1,4 +1,3 @@
export enum SidebarName { export enum SidebarName {
Settings = 'settings', Settings = 'settings',
Tag = 'tag', Tag = 'tag',

View file

@ -17,7 +17,7 @@ export const themeColors: Record<Theme, ThemeColors> = {
'linear-gradient(165deg, #ff7549 -50%, #ff3344 150%)', 'linear-gradient(165deg, #ff7549 -50%, #ff3344 150%)',
'linear-gradient(170deg, #582cd0, #592071)', 'linear-gradient(170deg, #582cd0, #592071)',
], ],
background: 'url(\'/images/noise_top.png\') 0% 0% #23201f', background: "url('/images/noise_top.png') 0% 0% #23201f",
}, },
[Theme.Horizon]: { [Theme.Horizon]: {
name: 'Веспера', name: 'Веспера',

View file

@ -37,7 +37,7 @@ export const imagePresets = {
flow_horizontal: 'flow_horizontal', flow_horizontal: 'flow_horizontal',
} as const; } as const;
export type ImagePreset = typeof imagePresets[keyof typeof imagePresets]; export type ImagePreset = (typeof imagePresets)[keyof typeof imagePresets];
export const imageSrcSets: Partial<Record<ImagePreset, number>> = { export const imageSrcSets: Partial<Record<ImagePreset, number>> = {
[imagePresets[1600]]: 1600, [imagePresets[1600]]: 1600,
@ -49,7 +49,7 @@ export const imageSrcSets: Partial<Record<ImagePreset, number>> = {
export const flowDisplayToPreset: Record< export const flowDisplayToPreset: Record<
FlowDisplayVariant, FlowDisplayVariant,
typeof imagePresets[keyof typeof imagePresets] (typeof imagePresets)[keyof typeof imagePresets]
> = { > = {
single: 'flow_square', single: 'flow_square',
quadro: 'flow_square', quadro: 'flow_square',

View file

@ -3,10 +3,12 @@ import dynamic from 'next/dynamic';
import type { BorisSuperpowersProps } from './index'; import type { BorisSuperpowersProps } from './index';
export const BorisSuperPowersSSR = dynamic<BorisSuperpowersProps>( export const BorisSuperPowersSSR = dynamic<BorisSuperpowersProps>(
() => import('~/containers/boris/BorisSuperpowers/index') () =>
.then(it => it.BorisSuperpowers), import('~/containers/boris/BorisSuperpowers/index').then(
(it) => it.BorisSuperpowers,
),
{ {
ssr: false, ssr: false,
loading: () => <div />, loading: () => <div />,
} },
); );

View file

@ -8,7 +8,7 @@ import { DialogComponentProps } from '~/types/modal';
import { values } from '~/utils/ramda'; import { values } from '~/utils/ramda';
export interface EditorCreateDialogProps extends DialogComponentProps { export interface EditorCreateDialogProps extends DialogComponentProps {
type: typeof NODE_TYPES[keyof typeof NODE_TYPES]; type: (typeof NODE_TYPES)[keyof typeof NODE_TYPES];
isInLab: boolean; isInLab: boolean;
} }

View file

@ -11,7 +11,7 @@ import { TextEditor } from '../components/TextEditor';
import { VideoEditor } from '../components/VideoEditor'; import { VideoEditor } from '../components/VideoEditor';
export const NODE_EDITORS: Record< export const NODE_EDITORS: Record<
typeof NODE_TYPES[keyof typeof NODE_TYPES], (typeof NODE_TYPES)[keyof typeof NODE_TYPES],
FC<NodeEditorProps> FC<NodeEditorProps>
> = { > = {
[NODE_TYPES.IMAGE]: ImageEditor, [NODE_TYPES.IMAGE]: ImageEditor,
@ -22,7 +22,7 @@ export const NODE_EDITORS: Record<
}; };
export const NODE_EDITOR_DATA: Record< export const NODE_EDITOR_DATA: Record<
typeof NODE_TYPES[keyof typeof NODE_TYPES], (typeof NODE_TYPES)[keyof typeof NODE_TYPES],
Partial<INode> Partial<INode>
> = { > = {
[NODE_TYPES.TEXT]: { [NODE_TYPES.TEXT]: {

View file

@ -4,9 +4,12 @@ import type { HeaderProps } from '~/containers/main/Header/index';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
export const HeaderSSR = dynamic<HeaderProps>(() => import('./index').then(it => it.Header), { export const HeaderSSR = dynamic<HeaderProps>(
() => import('./index').then((it) => it.Header),
{
ssr: false, ssr: false,
loading: () => <div className={styles.wrap} />, loading: () => <div className={styles.wrap} />,
}); },
);
export const HeaderSSRPlaceholder = () => <div className={styles.wrap} />; export const HeaderSSRPlaceholder = () => <div className={styles.wrap} />;

View file

@ -3,5 +3,6 @@ import dynamic from 'next/dynamic';
import type { SubmitBarProps } from './index'; import type { SubmitBarProps } from './index';
export const SubmitBarSSR = dynamic<SubmitBarProps>( export const SubmitBarSSR = dynamic<SubmitBarProps>(
() => import('./index').then(it => it.SubmitBar), () => import('./index').then((it) => it.SubmitBar),
{ ssr: false }); { ssr: false },
);

View file

@ -14,7 +14,7 @@ import type { SidebarComponentProps } from '~/types/sidebar';
import { isNil } from '~/utils/ramda'; import { isNil } from '~/utils/ramda';
const tabs = ['profile', 'notifications', 'bookmarks'] as const; const tabs = ['profile', 'notifications', 'bookmarks'] as const;
type TabName = typeof tabs[number]; type TabName = (typeof tabs)[number];
interface SettingsSidebarProps interface SettingsSidebarProps
extends SidebarComponentProps<SidebarName.Settings> { extends SidebarComponentProps<SidebarName.Settings> {

View file

@ -10,7 +10,7 @@ export const useLastSeenBoris = () => {
async (date: string) => { async (date: string) => {
await update({ last_seen_boris: date }, false); await update({ last_seen_boris: date }, false);
}, },
[update] [update],
); );
return { setLastSeen, lastSeen }; return { setLastSeen, lastSeen };

View file

@ -20,7 +20,7 @@ export const useLoginLogoutRestore = () => {
showToastInfo(getRandomPhrase('WELCOME')); showToastInfo(getRandomPhrase('WELCOME'));
return result.user; return result.user;
}, },
[auth] [auth],
); );
return { logout, login }; return { logout, login };

View file

@ -5,8 +5,9 @@ import { API } from '~/constants/api';
import { getErrorMessage } from '~/utils/errors/getErrorMessage'; import { getErrorMessage } from '~/utils/errors/getErrorMessage';
export const useRestoreCode = (code: string) => { export const useRestoreCode = (code: string) => {
const { data, isValidating, error } = useSWR(API.USER.REQUEST_CODE(code), () => const { data, isValidating, error } = useSWR(
apiCheckRestoreCode({ code }) API.USER.REQUEST_CODE(code),
() => apiCheckRestoreCode({ code }),
); );
const codeUser = data?.user; const codeUser = data?.user;

View file

@ -18,7 +18,7 @@ const validationSchema = object({
.test( .test(
'sameAsPassword', 'sameAsPassword',
'Должен совпадать с паролем', 'Должен совпадать с паролем',
(val, ctx) => val === ctx.parent.newPassword (val, ctx) => val === ctx.parent.newPassword,
), ),
}); });
@ -26,15 +26,21 @@ export type RestorePasswordData = Asserts<typeof validationSchema>;
export const useRestorePasswordForm = ( export const useRestorePasswordForm = (
code: string, code: string,
fetcher: (props: { code: string; password: string }) => Promise<{ token: string; user: IUser }>, fetcher: (props: {
onSuccess: () => void code: string;
password: string;
}) => Promise<{ token: string; user: IUser }>,
onSuccess: () => void,
) => { ) => {
const auth = useAuthStore(); const auth = useAuthStore();
const onSubmit = useCallback<FormikConfig<RestorePasswordData>['onSubmit']>( const onSubmit = useCallback<FormikConfig<RestorePasswordData>['onSubmit']>(
async (values, { setErrors }) => { async (values, { setErrors }) => {
try { try {
const { token, user } = await fetcher({ password: values.newPassword, code }); const { token, user } = await fetcher({
password: values.newPassword,
code,
});
auth.setUser(user); auth.setUser(user);
auth.setToken(token); auth.setToken(token);
onSuccess(); onSuccess();
@ -47,7 +53,7 @@ export const useRestorePasswordForm = (
} }
} }
}, },
[onSuccess, fetcher, code, auth] [onSuccess, fetcher, code, auth],
); );
return useFormik<RestorePasswordData>({ return useFormik<RestorePasswordData>({

View file

@ -15,7 +15,7 @@ type RestoreRequestData = Asserts<typeof validationSchema>;
export const useRestoreRequestForm = ( export const useRestoreRequestForm = (
fetcher: (field: string) => Promise<unknown>, fetcher: (field: string) => Promise<unknown>,
onSuccess: () => void onSuccess: () => void,
) => { ) => {
const onSubmit = useCallback<FormikConfig<RestoreRequestData>['onSubmit']>( const onSubmit = useCallback<FormikConfig<RestoreRequestData>['onSubmit']>(
async (values, { setErrors }) => { async (values, { setErrors }) => {
@ -31,7 +31,7 @@ export const useRestoreRequestForm = (
} }
} }
}, },
[fetcher, onSuccess] [fetcher, onSuccess],
); );
return useFormik({ return useFormik({

View file

@ -13,6 +13,6 @@ export const useSessionCookie = () => {
autorun(() => { autorun(() => {
setCookie('session', auth.token, 30); setCookie('session', auth.token, 30);
}), }),
[auth] [auth],
); );
}; };

View file

@ -9,9 +9,7 @@ import { showErrorToast } from '~/utils/errors/showToast';
const validationSchema = object({ const validationSchema = object({
username: string().required(ERRORS.REQUIRED), username: string().required(ERRORS.REQUIRED),
password: string() password: string().required(ERRORS.REQUIRED).min(6, ERRORS.PASSWORD_IS_SHORT),
.required(ERRORS.REQUIRED)
.min(6, ERRORS.PASSWORD_IS_SHORT),
}); });
type SocialRegisterData = Asserts<typeof validationSchema>; type SocialRegisterData = Asserts<typeof validationSchema>;
@ -23,7 +21,7 @@ export const useSocialRegisterForm = (
username: string; username: string;
password: string; password: string;
}) => Promise<{ token: string }>, }) => Promise<{ token: string }>,
onSuccess: (token: string) => void onSuccess: (token: string) => void,
) => { ) => {
const onSubmit = useCallback<FormikConfig<SocialRegisterData>['onSubmit']>( const onSubmit = useCallback<FormikConfig<SocialRegisterData>['onSubmit']>(
async (values, { setErrors }) => { async (values, { setErrors }) => {
@ -43,7 +41,7 @@ export const useSocialRegisterForm = (
} }
} }
}, },
[token, onSuccess, fetcher] [token, onSuccess, fetcher],
); );
return useFormik<SocialRegisterData>({ return useFormik<SocialRegisterData>({

View file

@ -7,7 +7,10 @@ const today = new Date();
export const useUserActiveStatus = (lastSeen?: string) => { export const useUserActiveStatus = (lastSeen?: string) => {
try { try {
const lastSeenDate = lastSeen ? parseISO(lastSeen) : undefined; const lastSeenDate = lastSeen ? parseISO(lastSeen) : undefined;
return lastSeenDate && differenceInDays(today, lastSeenDate) < INACTIVE_ACCOUNT_DAYS; return (
lastSeenDate &&
differenceInDays(today, lastSeenDate) < INACTIVE_ACCOUNT_DAYS
);
} catch (e) { } catch (e) {
return false; return false;
} }

View file

@ -6,12 +6,14 @@ import { initialBackendStats } from '~/constants/boris/constants';
import { BorisUsageStats } from '~/types/boris'; import { BorisUsageStats } from '~/types/boris';
export const useBorisStats = () => { export const useBorisStats = () => {
const { data: backend = initialBackendStats, isValidating: isValidatingBackend } = useSWR( const {
API.BORIS.GET_BACKEND_STATS, data: backend = initialBackendStats,
() => getBorisBackendStats() isValidating: isValidatingBackend,
); } = useSWR(API.BORIS.GET_BACKEND_STATS, () => getBorisBackendStats());
const { data: issues = [] } = useSWR(API.BORIS.GITHUB_ISSUES, () => getGithubIssues()); const { data: issues = [] } = useSWR(API.BORIS.GITHUB_ISSUES, () =>
getGithubIssues(),
);
const stats: BorisUsageStats = { const stats: BorisUsageStats = {
backend, backend,

View file

@ -3,9 +3,16 @@ import { useMemo } from 'react';
import { normalizeBrightColor } from '~/utils/color'; import { normalizeBrightColor } from '~/utils/color';
import { stringToColour } from '~/utils/dom'; import { stringToColour } from '~/utils/dom';
export const useColorFromString = (val?: string, saturation = 3, lightness = 3) => { export const useColorFromString = (
val?: string,
saturation = 3,
lightness = 3,
) => {
return useMemo( return useMemo(
() => (val && normalizeBrightColor(stringToColour(val), saturation, lightness)) || '', () =>
[lightness, saturation, val] (val &&
normalizeBrightColor(stringToColour(val), saturation, lightness)) ||
'',
[lightness, saturation, val],
); );
}; };

View file

@ -6,7 +6,7 @@ export const useColorGradientFromString = (
val?: string, val?: string,
saturation = 3, saturation = 3,
lightness = 3, lightness = 3,
angle = 155 angle = 155,
) => ) =>
useMemo(() => { useMemo(() => {
if (!val) { if (!val) {

View file

@ -1,6 +1,9 @@
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
export const usePersistedState = (key: string, initial: string): [string, (val: string) => any] => { export const usePersistedState = (
key: string,
initial: string,
): [string, (val: string) => any] => {
const stored = useMemo(() => { const stored = useMemo(() => {
try { try {
return localStorage.getItem(`vault_${key}`) || initial; return localStorage.getItem(`vault_${key}`) || initial;

View file

@ -4,15 +4,18 @@ export const useFocusEvent = (initialState = false, delay = 0) => {
const [focused, setFocused] = useState(initialState); const [focused, setFocused] = useState(initialState);
const onFocus = useCallback( const onFocus = useCallback(
event => { (event) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
setFocused(true); setFocused(true);
}, },
[setFocused] [setFocused],
);
const onBlur = useCallback(
() => setTimeout(() => setFocused(false), delay),
[delay],
); );
const onBlur = useCallback(() => setTimeout(() => setFocused(false), delay), [delay]);
return { focused, onBlur, onFocus }; return { focused, onBlur, onFocus };
}; };

View file

@ -7,12 +7,13 @@ export const useFormatWrapper = (onChange: (val: string) => void) => {
target: HTMLTextAreaElement, target: HTMLTextAreaElement,
prefix = '', prefix = '',
suffix = '' suffix = '',
) => event => { ) =>
(event) => {
event.preventDefault(); event.preventDefault();
wrapTextInsideInput(target, prefix, suffix, onChange); wrapTextInsideInput(target, prefix, suffix, onChange);
}, },
[onChange] [onChange],
); );
}; };
@ -21,7 +22,7 @@ export const wrapTextInsideInput = (
target: HTMLTextAreaElement, target: HTMLTextAreaElement,
prefix: string, prefix: string,
suffix: string, suffix: string,
onChange: (val: string) => void onChange: (val: string) => void,
) => { ) => {
if (!target) return; if (!target) return;
@ -34,7 +35,7 @@ export const wrapTextInsideInput = (
onChange( onChange(
target.value.substring(0, start) + target.value.substring(0, start) +
replacement + replacement +
target.value.substring(end, target.value.length) target.value.substring(end, target.value.length),
); );
target.focus(); target.focus();

View file

@ -2,7 +2,8 @@ import { useCallback, useEffect } from 'react';
export const useInfiniteLoader = (loader: () => void, isLoading?: boolean) => { export const useInfiniteLoader = (loader: () => void, isLoading?: boolean) => {
const onLoadMore = useCallback(() => { const onLoadMore = useCallback(() => {
const pos = window.scrollY + window.innerHeight - document.body.scrollHeight; const pos =
window.scrollY + window.innerHeight - document.body.scrollHeight;
if (isLoading || pos < -600) return; if (isLoading || pos < -600) return;

View file

@ -5,13 +5,13 @@ import { getImageFromPaste } from '~/utils/uploader';
// useInputPasteUpload attaches event listener to input, that calls onUpload if user pasted any image // useInputPasteUpload attaches event listener to input, that calls onUpload if user pasted any image
export const useInputPasteUpload = (onUpload: (files: File[]) => void) => { export const useInputPasteUpload = (onUpload: (files: File[]) => void) => {
return useCallback( return useCallback(
async event => { async (event) => {
const image = await getImageFromPaste(event); const image = await getImageFromPaste(event);
if (!image) return; if (!image) return;
onUpload([image]); onUpload([image]);
}, },
[onUpload] [onUpload],
); );
}; };

View file

@ -17,7 +17,11 @@ const sameWidth = {
}, },
}; };
export const usePopperModifiers = (offsetX = 0, offsetY = 10, justify?: boolean): Modifier<any>[] => export const usePopperModifiers = (
offsetX = 0,
offsetY = 10,
justify?: boolean,
): Modifier<any>[] =>
useMemo( useMemo(
() => () =>
[ [
@ -35,5 +39,5 @@ export const usePopperModifiers = (offsetX = 0, offsetY = 10, justify?: boolean)
}, },
...(justify ? [sameWidth] : []), ...(justify ? [sameWidth] : []),
] as Modifier<any>[], ] as Modifier<any>[],
[offsetX, offsetY, justify] [offsetX, offsetY, justify],
); );

View file

@ -11,7 +11,7 @@ const getHeight = () => {
body.offsetHeight, body.offsetHeight,
html.clientHeight, html.clientHeight,
html.scrollHeight, html.scrollHeight,
html.offsetHeight html.offsetHeight,
); );
}; };
export const useScrollHeight = () => getHeight(); export const useScrollHeight = () => getHeight();

View file

@ -18,6 +18,6 @@ export const useScrollToTop = (deps?: any[]) => {
}); });
}, },
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
deps && Array.isArray(deps) ? deps : [] deps && Array.isArray(deps) ? deps : [],
); );
}; };

View file

@ -1,7 +1,9 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
export const useScrollTop = () => { export const useScrollTop = () => {
const [top, setTop] = useState(typeof window !== 'undefined' ? window.scrollY : 0); const [top, setTop] = useState(
typeof window !== 'undefined' ? window.scrollY : 0,
);
useEffect(() => { useEffect(() => {
setTop(window.scrollY); setTop(window.scrollY);

View file

@ -6,11 +6,12 @@ export const useFlowCellControls = (
id: INode['id'], id: INode['id'],
description: string | undefined, description: string | undefined,
flow: FlowDisplay, flow: FlowDisplay,
onChangeCellView: (id: INode['id'], flow: FlowDisplay) => void onChangeCellView: (id: INode['id'], flow: FlowDisplay) => void,
) => { ) => {
const onChange = useCallback( const onChange = useCallback(
(value: Partial<FlowDisplay>) => onChangeCellView(id, { ...flow, ...value }), (value: Partial<FlowDisplay>) =>
[flow, id, onChangeCellView] onChangeCellView(id, { ...flow, ...value }),
[flow, id, onChangeCellView],
); );
const hasDescription = !!description && description.length > 32; const hasDescription = !!description && description.length > 32;

View file

@ -17,6 +17,6 @@ export const useFlowSetCellView = () => {
showErrorToast(error); showErrorToast(error);
} }
}, },
[updateNode] [updateNode],
); );
}; };

View file

@ -21,15 +21,19 @@ export const useGetLabStats = () => {
heroes: lab.heroes, heroes: lab.heroes,
tags: lab.tags, tags: lab.tags,
}, },
onSuccess: data => { onSuccess: (data) => {
lab.setHeroes(data.heroes); lab.setHeroes(data.heroes);
lab.setTags(data.tags); lab.setTags(data.tags);
}, },
refreshInterval, refreshInterval,
} },
); );
const { data: updatesData, isValidating: isValidatingUpdates, mutate: mutateUpdates } = useSWR( const {
data: updatesData,
isValidating: isValidatingUpdates,
mutate: mutateUpdates,
} = useSWR(
isUser ? API.LAB.UPDATES : null, isUser ? API.LAB.UPDATES : null,
async () => { async () => {
const result = await getLabUpdates(); const result = await getLabUpdates();
@ -37,26 +41,27 @@ export const useGetLabStats = () => {
}, },
{ {
fallbackData: lab.updates, fallbackData: lab.updates,
onSuccess: data => { onSuccess: (data) => {
lab.setUpdates(data); lab.setUpdates(data);
}, },
refreshInterval, refreshInterval,
} },
); );
const heroes = useMemo(() => stats?.heroes || [], [stats]); const heroes = useMemo(() => stats?.heroes || [], [stats]);
const tags = useMemo(() => stats?.tags || [], [stats]); const tags = useMemo(() => stats?.tags || [], [stats]);
const updates = useMemo(() => updatesData || [], [updatesData]); const updates = useMemo(() => updatesData || [], [updatesData]);
const isLoading = (!stats || !updates) && (isValidatingStats || isValidatingUpdates); const isLoading =
(!stats || !updates) && (isValidatingStats || isValidatingUpdates);
const seenNode = useCallback( const seenNode = useCallback(
async (nodeId: number) => { async (nodeId: number) => {
await mutateUpdates( await mutateUpdates(
updates.filter(it => it.id !== nodeId), updates.filter((it) => it.id !== nodeId),
false false,
); );
}, },
[mutateUpdates, updates] [mutateUpdates, updates],
); );
return { heroes, tags, updates, isLoading, seenNode }; return { heroes, tags, updates, isLoading, seenNode };

View file

@ -11,7 +11,7 @@ const getKey = (username: string): string | null => {
}; };
export const useMessages = (username: string) => { export const useMessages = (username: string) => {
const { data, isValidating } = useSWR(getKey(username), async () => const { data, isValidating } = useSWR(getKey(username), async () =>
apiGetUserMessages({ username }) apiGetUserMessages({ username }),
); );
const messages: IMessage[] = useMemo(() => data?.messages || [], [data]); const messages: IMessage[] = useMemo(() => data?.messages || [], [data]);

View file

@ -5,7 +5,9 @@ import { useModalStore } from '~/store/modal/useModalStore';
import { DialogComponentProps } from '~/types/modal'; import { DialogComponentProps } from '~/types/modal';
export type DialogContentProps = { export type DialogContentProps = {
[K in keyof typeof DIALOG_CONTENT]: typeof DIALOG_CONTENT[K] extends (props: infer U) => any [K in keyof typeof DIALOG_CONTENT]: (typeof DIALOG_CONTENT)[K] extends (
props: infer U,
) => any
? U extends DialogComponentProps ? U extends DialogComponentProps
? keyof Omit<U, 'onRequestClose' | 'children'> extends never ? keyof Omit<U, 'onRequestClose' | 'children'> extends never
? {} ? {}
@ -21,7 +23,7 @@ export const useModal = () => {
<T extends Dialog>(dialog: T, props: DialogContentProps[T]) => { <T extends Dialog>(dialog: T, props: DialogContentProps[T]) => {
setCurrent(dialog, props); setCurrent(dialog, props);
}, },
[setCurrent] [setCurrent],
); );
return { showModal, hideModal: hide, current, isOpened: !!current }; return { showModal, hideModal: hide, current, isOpened: !!current };

View file

@ -10,6 +10,6 @@ export const useShowModal = <T extends Dialog>(dialog: T) => {
(props: DialogContentProps[T]) => { (props: DialogContentProps[T]) => {
modal.showModal(dialog, props); modal.showModal(dialog, props);
}, },
[dialog, modal] [dialog, modal],
); );
}; };

View file

@ -11,6 +11,6 @@ export const useImageModal = () => {
(images: IFile[], index: number) => { (images: IFile[], index: number) => {
showModal({ items: images, index }); showModal({ items: images, index });
}, },
[showModal] [showModal],
); );
}; };

View file

@ -17,7 +17,7 @@ export const useNavigation = () => {
craHistory.push(url); craHistory.push(url);
} }
}, },
[craHistory, nextRouter] [craHistory, nextRouter],
); );
return { push }; return { push };

View file

@ -16,9 +16,13 @@ export const useCreateNode = () => {
if (node.is_promoted) { if (node.is_promoted) {
flow.setNodes([result.node, ...flow.nodes]); flow.setNodes([result.node, ...flow.nodes]);
} else { } else {
await lab.unshift({ node: result.node, comment_count: 0, last_seen: node.created_at }); await lab.unshift({
node: result.node,
comment_count: 0,
last_seen: node.created_at,
});
} }
}, },
[flow, lab] [flow, lab],
); );
}; };

View file

@ -6,13 +6,13 @@ import { groupCommentsByUser } from '~/utils/fn';
export const useGrouppedComments = ( export const useGrouppedComments = (
comments: IComment[], comments: IComment[],
order: 'ASC' | 'DESC', order: 'ASC' | 'DESC',
lastSeen?: string lastSeen?: string,
) => ) =>
useMemo( useMemo(
() => () =>
(order === 'DESC' ? [...comments].reverse() : comments).reduce( (order === 'DESC' ? [...comments].reverse() : comments).reduce(
groupCommentsByUser(lastSeen), groupCommentsByUser(lastSeen),
[] [],
), ),
[comments, lastSeen, order] [comments, lastSeen, order],
); );

View file

@ -6,7 +6,10 @@ import { useModal } from '~/hooks/modal/useModal';
import { INode } from '~/types'; import { INode } from '~/types';
import { showErrorToast } from '~/utils/errors/showToast'; import { showErrorToast } from '~/utils/errors/showToast';
export const useNodeActions = (node: INode, update: (node: Partial<INode>) => Promise<unknown>) => { export const useNodeActions = (
node: INode,
update: (node: Partial<INode>) => Promise<unknown>,
) => {
const { showModal } = useModal(); const { showModal } = useModal();
const onLike = useCallback(async () => { const onLike = useCallback(async () => {
@ -35,17 +38,20 @@ export const useNodeActions = (node: INode, update: (node: Partial<INode>) => Pr
const onLock = useCallback(async () => { const onLock = useCallback(async () => {
try { try {
const result = await apiLockNode({ id: node.id, is_locked: !node.deleted_at }); const result = await apiLockNode({
id: node.id,
is_locked: !node.deleted_at,
});
await update({ deleted_at: result.deleted_at }); await update({ deleted_at: result.deleted_at });
} catch (error) { } catch (error) {
showErrorToast(error); showErrorToast(error);
} }
}, [node.deleted_at, node.id, update]); }, [node.deleted_at, node.id, update]);
const onEdit = useCallback(() => showModal(Dialog.EditNode, { nodeId: node.id! }), [ const onEdit = useCallback(
node, () => showModal(Dialog.EditNode, { nodeId: node.id! }),
showModal, [node, showModal],
]); );
return { onLike, onStar, onLock, onEdit }; return { onLike, onStar, onLock, onEdit };
}; };

View file

@ -4,7 +4,8 @@ import { UploadType } from '~/constants/uploads';
import { INode } from '~/types'; import { INode } from '~/types';
export const useNodeAudios = (node: INode) => { export const useNodeAudios = (node: INode) => {
return useMemo(() => node.files.filter(file => file && file.type === UploadType.Audio), [ return useMemo(
node.files, () => node.files.filter((file) => file && file.type === UploadType.Audio),
]); [node.files],
);
}; };

View file

@ -1,6 +1,11 @@
import { useCallback, useRef } from 'react'; import { useCallback, useRef } from 'react';
import { FormikConfig, FormikHelpers, useFormik, useFormikContext } from 'formik'; import {
FormikConfig,
FormikHelpers,
useFormik,
useFormikContext,
} from 'formik';
import { object } from 'yup'; import { object } from 'yup';
import { INode } from '~/types'; import { INode } from '~/types';
@ -10,9 +15,9 @@ import { showErrorToast } from '~/utils/errors/showToast';
const validationSchema = object().shape({}); const validationSchema = object().shape({});
const afterSubmit = ({ resetForm, setSubmitting, setErrors }: FormikHelpers<INode>) => ( const afterSubmit =
error?: unknown ({ resetForm, setSubmitting, setErrors }: FormikHelpers<INode>) =>
) => { (error?: unknown) => {
setSubmitting(false); setSubmitting(false);
if (error) { if (error) {
@ -28,13 +33,13 @@ const afterSubmit = ({ resetForm, setSubmitting, setErrors }: FormikHelpers<INod
if (resetForm) { if (resetForm) {
resetForm(); resetForm();
} }
}; };
export const useNodeFormFormik = ( export const useNodeFormFormik = (
values: INode, values: INode,
uploader: Uploader, uploader: Uploader,
stopEditing: () => void, stopEditing: () => void,
sendSaveRequest: (node: INode) => Promise<unknown> sendSaveRequest: (node: INode) => Promise<unknown>,
) => { ) => {
const { current: initialValues } = useRef(values); const { current: initialValues } = useRef(values);
@ -53,7 +58,7 @@ export const useNodeFormFormik = (
afterSubmit(helpers)(error); afterSubmit(helpers)(error);
} }
}, },
[sendSaveRequest, uploader.files] [sendSaveRequest, uploader.files],
); );
return useFormik<INode>({ return useFormik<INode>({

View file

@ -4,7 +4,8 @@ import { UploadType } from '~/constants/uploads';
import { INode } from '~/types'; import { INode } from '~/types';
export const useNodeImages = (node: INode) => { export const useNodeImages = (node: INode) => {
return useMemo(() => node.files.filter(file => file && file.type === UploadType.Image), [ return useMemo(
node.files, () => node.files.filter((file) => file && file.type === UploadType.Image),
]); [node.files],
);
}; };

View file

@ -27,6 +27,6 @@ export const useUpdateNode = (id: number) => {
await lab.updateNode(result.node.id!, result.node); await lab.updateNode(result.node.id!, result.node);
} }
}, },
[update, flow, lab] [update, flow, lab],
); );
}; };

View file

@ -20,7 +20,7 @@ export const useGetProfile = (username?: string) => {
}, },
{ {
refreshInterval: 60000, refreshInterval: 60000,
} },
); );
const profile = data || EMPTY_USER; const profile = data || EMPTY_USER;
@ -29,7 +29,7 @@ export const useGetProfile = (username?: string) => {
async (user: Partial<IUser>) => { async (user: Partial<IUser>) => {
await mutate({ ...profile, ...user }); await mutate({ ...profile, ...user });
}, },
[mutate, profile] [mutate, profile],
); );
return { profile, isLoading: !data && isValidating, update }; return { profile, isLoading: !data && isValidating, update };

View file

@ -9,10 +9,8 @@ import { flatten } from '~/utils/ramda';
const RESULTS_COUNT = 20; const RESULTS_COUNT = 20;
const getKey: (text: string) => SWRInfiniteKeyLoader = text => ( const getKey: (text: string) => SWRInfiniteKeyLoader =
pageIndex, (text) => (pageIndex, previousPageData: INode[]) => {
previousPageData: INode[]
) => {
if ((pageIndex > 0 && !previousPageData?.length) || !text) return null; if ((pageIndex > 0 && !previousPageData?.length) || !text) return null;
const props: GetSearchResultsRequest = { const props: GetSearchResultsRequest = {
@ -22,7 +20,7 @@ const getKey: (text: string) => SWRInfiniteKeyLoader = text => (
}; };
return JSON.stringify(props); return JSON.stringify(props);
}; };
export const useSearch = () => { export const useSearch = () => {
const [searchText, setSearchText] = useState(''); const [searchText, setSearchText] = useState('');
@ -40,7 +38,7 @@ export const useSearch = () => {
const result = await getSearchResults(props); const result = await getSearchResults(props);
return result.nodes; return result.nodes;
} },
); );
const loadMore = useCallback(() => setSize(size + 1), [setSize, size]); const loadMore = useCallback(() => setSize(size + 1), [setSize, size]);

View file

@ -26,5 +26,5 @@ export const useTagAutocomplete = (
}, },
); );
return useMemo(() => (search ? data ?? [] : []), [data, search]); return useMemo(() => (search ? (data ?? []) : []), [data, search]);
}; };

View file

@ -9,13 +9,11 @@ import { flatten, isNil } from '~/utils/ramda';
const PAGE_SIZE = 10; const PAGE_SIZE = 10;
const getKey: (tag: string) => SWRInfiniteKeyLoader = tag => ( const getKey: (tag: string) => SWRInfiniteKeyLoader =
pageIndex, (tag) => (pageIndex, previousPageData: INode[]) => {
previousPageData: INode[]
) => {
if (pageIndex > 0 && !previousPageData?.length) return null; if (pageIndex > 0 && !previousPageData?.length) return null;
return `${API.TAG.NODES}?tag=${tag}&page=${pageIndex}`; return `${API.TAG.NODES}?tag=${tag}&page=${pageIndex}`;
}; };
const extractKey = (key: string) => { const extractKey = (key: string) => {
const re = new RegExp(`${API.TAG.NODES}\\?tag=[^&]+&page=(\\d+)`); const re = new RegExp(`${API.TAG.NODES}\\?tag=[^&]+&page=(\\d+)`);
@ -39,7 +37,7 @@ export const useTagNodes = (tag: string) => {
}); });
return result.nodes; return result.nodes;
} },
); );
const nodes = useMemo(() => flatten(data || []), [data]); const nodes = useMemo(() => flatten(data || []), [data]);
@ -47,5 +45,12 @@ export const useTagNodes = (tag: string) => {
const loadMore = useCallback(() => setSize(size + 1), [setSize, size]); const loadMore = useCallback(() => setSize(size + 1), [setSize, size]);
return { nodes, hasMore, loadMore, isLoading: !data && isValidating, mutate, data }; return {
nodes,
hasMore,
loadMore,
isLoading: !data && isValidating,
mutate,
data,
};
}; };

View file

@ -9,7 +9,7 @@ export const useUpdates = () => {
const { data } = useSWR( const { data } = useSWR(
isUser ? API.USER.GET_UPDATES : null, isUser ? API.USER.GET_UPDATES : null,
() => apiAuthGetUpdates({ exclude_dialogs: 0, last: '' }), () => apiAuthGetUpdates({ exclude_dialogs: 0, last: '' }),
{ refreshInterval: 5 * 60 * 1000 } { refreshInterval: 5 * 60 * 1000 },
); );
const borisCommentedAt = data?.boris?.commented_at || ''; const borisCommentedAt = data?.boris?.commented_at || '';

View file

@ -1,4 +1,4 @@
const reportWebVitals = onPerfEntry => { const reportWebVitals = (onPerfEntry) => {
if (onPerfEntry && onPerfEntry instanceof Function) { if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry); getCLS(onPerfEntry);

View file

@ -24,11 +24,13 @@ export class FlowStore {
/** removes node from updated after user seen it */ /** removes node from updated after user seen it */
seenNode = (nodeId: number) => { seenNode = (nodeId: number) => {
this.setUpdated(this.updated.filter(node => node.id !== nodeId)); this.setUpdated(this.updated.filter((node) => node.id !== nodeId));
}; };
/** replaces node with value */ /** replaces node with value */
updateNode = (id: number, node: Partial<IFlowNode>) => { updateNode = (id: number, node: Partial<IFlowNode>) => {
this.setNodes(this.nodes.map(it => (it.id === id ? { ...it, ...node } : it))); this.setNodes(
this.nodes.map((it) => (it.id === id ? { ...it, ...node } : it)),
);
}; };
} }

View file

@ -12,7 +12,9 @@ export class MetadataStore {
pending: string[] = []; pending: string[] = [];
constructor( constructor(
protected apiMetadataLoader: (ids: string[]) => Promise<Record<string, EmbedMetadata>> protected apiMetadataLoader: (
ids: string[],
) => Promise<Record<string, EmbedMetadata>>,
) { ) {
makeAutoObservable(this); makeAutoObservable(this);
} }
@ -59,7 +61,7 @@ export class MetadataStore {
try { try {
const result = await this.apiMetadataLoader(items); const result = await this.apiMetadataLoader(items);
const fetchedIDs = values(result).map(it => it.address); const fetchedIDs = values(result).map((it) => it.address);
runInAction(() => { runInAction(() => {
this.pushMetadataItems(result); this.pushMetadataItems(result);
@ -72,7 +74,11 @@ export class MetadataStore {
/** adds items to queue */ /** adds items to queue */
enqueue = (id: string) => { enqueue = (id: string) => {
if (this.queue.includes(id) || keys(this.metadata).includes(id) || this.pending.includes(id)) { if (
this.queue.includes(id) ||
keys(this.metadata).includes(id) ||
this.pending.includes(id)
) {
return; return;
} }

View file

@ -153,13 +153,13 @@ export const NOTIFICATION_TYPES = {
}; };
export type IMessageNotification = { export type IMessageNotification = {
type: typeof NOTIFICATION_TYPES['message']; type: (typeof NOTIFICATION_TYPES)['message'];
content: Partial<IMessage>; content: Partial<IMessage>;
created_at: string; created_at: string;
}; };
export type ICommentNotification = { export type ICommentNotification = {
type: typeof NOTIFICATION_TYPES['comment']; type: (typeof NOTIFICATION_TYPES)['comment'];
content: Partial<IComment>; content: Partial<IComment>;
created_at: string; created_at: string;
}; };

View file

@ -5,9 +5,8 @@ import type { SidebarComponents } from '~/constants/sidebar/components';
export type SidebarComponent = keyof SidebarComponents; export type SidebarComponent = keyof SidebarComponents;
// TODO: use it to store props for sidebar // TODO: use it to store props for sidebar
export type SidebarProps< export type SidebarProps<T extends SidebarComponent> =
T extends SidebarComponent SidebarComponents[T] extends FunctionComponent<infer U>
> = SidebarComponents[T] extends FunctionComponent<infer U>
? U extends object ? U extends object
? U extends SidebarComponentProps<T> ? U extends SidebarComponentProps<T>
? Omit<U, keyof SidebarComponentProps<T>> ? Omit<U, keyof SidebarComponentProps<T>>

View file

@ -1,9 +1,19 @@
import { adjustHue, darken, desaturate, parseToHsla, transparentize } from 'color2k'; import {
adjustHue,
darken,
desaturate,
parseToHsla,
transparentize,
} from 'color2k';
import { DEFAULT_DOMINANT_COLOR } from '~/constants/node'; import { DEFAULT_DOMINANT_COLOR } from '~/constants/node';
import { stringToColour } from '~/utils/dom'; import { stringToColour } from '~/utils/dom';
export const normalizeBrightColor = (color?: string, saturationExp = 3, lightnessExp = 3) => { export const normalizeBrightColor = (
color?: string,
saturationExp = 3,
lightnessExp = 3,
) => {
if (!color) { if (!color) {
return ''; return '';
} }
@ -12,12 +22,23 @@ export const normalizeBrightColor = (color?: string, saturationExp = 3, lightnes
const saturation = hsla[1]; const saturation = hsla[1];
const lightness = hsla[2]; const lightness = hsla[2];
const desaturated = saturationExp > 1 ? desaturate(color, saturation ** saturationExp) : color; const desaturated =
return lightnessExp > 1 ? darken(desaturated, lightness ** lightnessExp) : desaturated; saturationExp > 1 ? desaturate(color, saturation ** saturationExp) : color;
return lightnessExp > 1
? darken(desaturated, lightness ** lightnessExp)
: desaturated;
}; };
export const generateColorTriplet = (val: string, saturation: number, lightness: number) => { export const generateColorTriplet = (
const color = normalizeBrightColor(stringToColour(val), saturation, lightness); val: string,
saturation: number,
lightness: number,
) => {
const color = normalizeBrightColor(
stringToColour(val),
saturation,
lightness,
);
return [ return [
color, color,
@ -31,9 +52,13 @@ export const generateGradientFromColor = (
saturation = 3, saturation = 3,
lightness = 3, lightness = 3,
angle = 155, angle = 155,
opacity = 1 opacity = 1,
) => { ) => {
const [first, second, third] = generateColorTriplet(val, saturation, lightness).map(it => { const [first, second, third] = generateColorTriplet(
val,
saturation,
lightness,
).map((it) => {
if (opacity > 1 || opacity < 0) { if (opacity > 1 || opacity < 0) {
return it; return it;
} }

View file

@ -6,5 +6,6 @@ export const CONFIG = {
// image storage endpoint (sames as backend, but with /static usualy) // image storage endpoint (sames as backend, but with /static usualy)
remoteCurrent: process.env.NEXT_PUBLIC_REMOTE_CURRENT || '', remoteCurrent: process.env.NEXT_PUBLIC_REMOTE_CURRENT || '',
// transitional prop, marks migration to nextjs // transitional prop, marks migration to nextjs
isNextEnvironment: !!process.env.NEXT_PUBLIC_REMOTE_CURRENT || typeof window === 'undefined', isNextEnvironment:
!!process.env.NEXT_PUBLIC_REMOTE_CURRENT || typeof window === 'undefined',
}; };

View file

@ -3,11 +3,19 @@ import { differenceInDays, isAfter, isValid, parseISO } from 'date-fns';
import { IComment, ICommentGroup } from '~/types'; import { IComment, ICommentGroup } from '~/types';
import { curry, insert, nth, path, remove } from '~/utils/ramda'; import { curry, insert, nth, path, remove } from '~/utils/ramda';
export const moveArrItem = curry((at, to, list) => insert(to, nth(at, list), remove(at, 1, list))); export const moveArrItem = curry((at, to, list) =>
insert(to, nth(at, list), remove(at, 1, list)),
);
export const objFromArray = (array: any[], key: string) => export const objFromArray = (array: any[], key: string) =>
array.reduce((obj, el) => (key && el[key] ? { ...obj, [el[key]]: el } : obj), {}); array.reduce(
(obj, el) => (key && el[key] ? { ...obj, [el[key]]: el } : obj),
{},
);
const compareCommentDates = (commentDateValue?: string, lastSeenDateValue?: string) => { const compareCommentDates = (
commentDateValue?: string,
lastSeenDateValue?: string,
) => {
if (!commentDateValue || !lastSeenDateValue) { if (!commentDateValue || !lastSeenDateValue) {
return false; return false;
} }
@ -37,11 +45,13 @@ const getCommentDistance = (firstDate?: string, secondDate?: string) => {
} }
}; };
export const groupCommentsByUser = (lastSeen?: string) => ( export const groupCommentsByUser =
grouppedComments: ICommentGroup[], (lastSeen?: string) =>
comment: IComment (grouppedComments: ICommentGroup[], comment: IComment): ICommentGroup[] => {
): ICommentGroup[] => { const last: ICommentGroup | undefined = path(
const last: ICommentGroup | undefined = path([grouppedComments.length - 1], grouppedComments); [grouppedComments.length - 1],
grouppedComments,
);
if (!comment.user) { if (!comment.user) {
return grouppedComments; return grouppedComments;
@ -69,13 +79,15 @@ export const groupCommentsByUser = (lastSeen?: string) => (
...last.distancesInDays, ...last.distancesInDays,
getCommentDistance( getCommentDistance(
comment?.created_at, comment?.created_at,
last.comments[last.comments.length - 1]?.created_at last.comments[last.comments.length - 1]?.created_at,
), ),
], ],
comments: [...last.comments, comment], comments: [...last.comments, comment],
ids: [...last.ids, comment.id], ids: [...last.ids, comment.id],
hasNew: last.hasNew || compareCommentDates(comment.created_at, lastSeen), hasNew:
last.hasNew ||
compareCommentDates(comment.created_at, lastSeen),
}, },
]), ]),
]; ];
}; };

View file

@ -18,11 +18,16 @@ const ProfileContext = createContext<ProfileContextValue>({
isLoading: false, isLoading: false,
}); });
export const ProfileProvider: FC<ProfileProviderProps> = ({ children, username }) => { export const ProfileProvider: FC<ProfileProviderProps> = ({
children,
username,
}) => {
const { profile, isLoading } = useGetProfile(username); const { profile, isLoading } = useGetProfile(username);
return ( return (
<ProfileContext.Provider value={{ profile, isLoading }}>{children}</ProfileContext.Provider> <ProfileContext.Provider value={{ profile, isLoading }}>
{children}
</ProfileContext.Provider>
); );
}; };

View file

@ -2,10 +2,12 @@ import { flatten, isEmpty } from '~/utils/ramda';
export const splitTextByYoutube = (strings: string[]): string[] => export const splitTextByYoutube = (strings: string[]): string[] =>
flatten( flatten(
strings.map(str => strings.map((str) =>
str.split(/(https?:\/\/(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch)?(?:\?v=)?[\w\-&=]+)/) str.split(
) /(https?:\/\/(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch)?(?:\?v=)?[\w\-&=]+)/,
),
),
); );
export const splitTextOmitEmpty = (strings: string[]): string[] => export const splitTextOmitEmpty = (strings: string[]): string[] =>
strings.map(el => el.trim()).filter(el => !isEmpty(el)); strings.map((el) => el.trim()).filter((el) => !isEmpty(el));

View file

@ -1,4 +1,4 @@
/** just combines title elements to form title of the page */ /** just combines title elements to form title of the page */
export const getPageTitle = (...props: string[]): string => { export const getPageTitle = (...props: string[]): string => {
return ['Убежище', ...props].filter(it => it.trim()).join(' • '); return ['Убежище', ...props].filter((it) => it.trim()).join(' • ');
}; };

View file

@ -3,11 +3,13 @@ import { ITag } from '~/types';
export const separateTags = (tags: Partial<ITag>[]): Partial<ITag>[][] => export const separateTags = (tags: Partial<ITag>[]): Partial<ITag>[][] =>
(tags || []).reduce( (tags || []).reduce(
(obj, tag) => (obj, tag) =>
tag?.title?.substr(0, 1) === '/' ? [[...obj[0], tag], obj[1]] : [obj[0], [...obj[1], tag]], tag?.title?.substr(0, 1) === '/'
[[], []] as Partial<ITag>[][] ? [[...obj[0], tag], obj[1]]
: [obj[0], [...obj[1], tag]],
[[], []] as Partial<ITag>[][],
); );
export const separateTagOptions = (options: string[]): string[][] => export const separateTagOptions = (options: string[]): string[][] =>
separateTags(options.map((title): Partial<ITag> => ({ title }))).map(item => separateTags(options.map((title): Partial<ITag> => ({ title }))).map((item) =>
item.filter(tag => tag.title).map(({ title }) => title!) item.filter((tag) => tag.title).map(({ title }) => title!),
); );

View file

@ -1,5 +1,6 @@
import { ERROR_LITERAL, ERRORS } from '~/constants/errors'; import { ERROR_LITERAL, ERRORS } from '~/constants/errors';
import { ValueOf } from '~/types'; import { ValueOf } from '~/types';
export const t = (string: ValueOf<typeof ERRORS>): ValueOf<typeof ERROR_LITERAL> => export const t = (
ERROR_LITERAL[string] || string; string: ValueOf<typeof ERRORS>,
): ValueOf<typeof ERROR_LITERAL> => ERROR_LITERAL[string] || string;

View file

@ -2,10 +2,10 @@ import { FILE_MIMES, UploadType } from '~/constants/uploads';
import { isMimeOfImage } from '~/utils/validators'; import { isMimeOfImage } from '~/utils/validators';
/** if file is image, returns data-uri of thumbnail */ /** if file is image, returns data-uri of thumbnail */
export const uploadGetThumb = async file => { export const uploadGetThumb = async (file) => {
if (!file.type || !isMimeOfImage(file.type)) return ''; if (!file.type || !isMimeOfImage(file.type)) return '';
return new Promise<string>(resolve => { return new Promise<string>((resolve) => {
const reader = new FileReader(); const reader = new FileReader();
reader.onloadend = () => resolve(reader.result?.toString() || ''); reader.onloadend = () => resolve(reader.result?.toString() || '');
reader.readAsDataURL(file); reader.readAsDataURL(file);
@ -15,14 +15,17 @@ export const uploadGetThumb = async file => {
/** returns UploadType by file */ /** returns UploadType by file */
export const getFileType = (file: File): UploadType | undefined => export const getFileType = (file: File): UploadType | undefined =>
((file.type && ((file.type &&
Object.keys(FILE_MIMES).find(mime => FILE_MIMES[mime].includes(file.type))) as UploadType) || Object.keys(FILE_MIMES).find((mime) =>
undefined; FILE_MIMES[mime].includes(file.type),
)) as UploadType) || undefined;
/** getImageFromPaste returns any images from paste event */ /** getImageFromPaste returns any images from paste event */
export const getImageFromPaste = (event: ClipboardEvent): Promise<File | undefined> => { export const getImageFromPaste = (
event: ClipboardEvent,
): Promise<File | undefined> => {
const items = event.clipboardData?.items; const items = event.clipboardData?.items;
return new Promise(resolve => { return new Promise((resolve) => {
for (let index in items) { for (let index in items) {
const item = items[index]; const item = items[index];
@ -31,7 +34,7 @@ export const getImageFromPaste = (event: ClipboardEvent): Promise<File | undefin
const reader = new FileReader(); const reader = new FileReader();
const type = item.type; const type = item.type;
reader.onload = function(e) { reader.onload = function (e) {
if (!e.target?.result) { if (!e.target?.result) {
return; return;
} }
@ -40,7 +43,7 @@ export const getImageFromPaste = (event: ClipboardEvent): Promise<File | undefin
new File([e.target?.result], 'paste.png', { new File([e.target?.result], 'paste.png', {
type, type,
lastModified: new Date().getTime(), lastModified: new Date().getTime(),
}) }),
); );
}; };

View file

@ -1,3 +1,4 @@
import { IMAGE_MIME_TYPES } from '~/constants/uploads'; import { IMAGE_MIME_TYPES } from '~/constants/uploads';
export const isMimeOfImage = (mime): boolean => !!mime && IMAGE_MIME_TYPES.indexOf(mime) >= 0; export const isMimeOfImage = (mime): boolean =>
!!mime && IMAGE_MIME_TYPES.indexOf(mime) >= 0;

View file

@ -221,6 +221,11 @@
"@nodelib/fs.scandir" "2.1.5" "@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0" fastq "^1.6.0"
"@pkgr/core@^0.1.0":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
"@polka/url@^1.0.0-next.20": "@polka/url@^1.0.0-next.20":
version "1.0.0-next.21" version "1.0.0-next.21"
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1"
@ -1206,6 +1211,14 @@ eslint-plugin-import@^2.25.4:
resolve "^1.20.0" resolve "^1.20.0"
tsconfig-paths "^3.12.0" tsconfig-paths "^3.12.0"
eslint-plugin-prettier@^5.2.3:
version "5.2.3"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz#c4af01691a6fa9905207f0fbba0d7bea0902cce5"
integrity sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==
dependencies:
prettier-linter-helpers "^1.0.0"
synckit "^0.9.1"
eslint-plugin-react-hooks@^4.6.0: eslint-plugin-react-hooks@^4.6.0:
version "4.6.0" version "4.6.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3"
@ -1394,6 +1407,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-diff@^1.1.2:
version "1.3.0"
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0"
integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
fast-fifo@^1.1.0, fast-fifo@^1.2.0: fast-fifo@^1.1.0, fast-fifo@^1.2.0:
version "1.3.2" version "1.3.2"
resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c"
@ -2452,10 +2470,17 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prettier@^2.7.1: prettier-linter-helpers@^1.0.0:
version "2.7.1" version "1.0.0"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b"
integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
dependencies:
fast-diff "^1.1.2"
prettier@^3.0.0:
version "3.4.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.2.tgz#a5ce1fb522a588bf2b78ca44c6e6fe5aa5a2b13f"
integrity sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==
pretty-format@^26.6.2: pretty-format@^26.6.2:
version "26.6.2" version "26.6.2"
@ -3087,6 +3112,14 @@ swr@^1.0.1:
resolved "https://registry.yarnpkg.com/swr/-/swr-1.2.0.tgz#8649f6e9131ce94bbcf7ffd65c21334da3d1ec20" resolved "https://registry.yarnpkg.com/swr/-/swr-1.2.0.tgz#8649f6e9131ce94bbcf7ffd65c21334da3d1ec20"
integrity sha512-C3IXeKOREn0jQ1ewXRENE7ED7jjGbFTakwB64eLACkCqkF/A0N2ckvpCTftcaSYi5yV36PzoehgVCOVRmtECcA== integrity sha512-C3IXeKOREn0jQ1ewXRENE7ED7jjGbFTakwB64eLACkCqkF/A0N2ckvpCTftcaSYi5yV36PzoehgVCOVRmtECcA==
synckit@^0.9.1:
version "0.9.2"
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.9.2.tgz#a3a935eca7922d48b9e7d6c61822ee6c3ae4ec62"
integrity sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==
dependencies:
"@pkgr/core" "^0.1.0"
tslib "^2.6.2"
table@^6.0.9: table@^6.0.9:
version "6.8.0" version "6.8.0"
resolved "https://registry.yarnpkg.com/table/-/table-6.8.0.tgz#87e28f14fa4321c3377ba286f07b79b281a3b3ca" resolved "https://registry.yarnpkg.com/table/-/table-6.8.0.tgz#87e28f14fa4321c3377ba286f07b79b281a3b3ca"
@ -3221,6 +3254,11 @@ tslib@^2.0.3, tslib@^2.1.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
tslib@^2.6.2:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
tsutils@^3.21.0: tsutils@^3.21.0:
version "3.21.0" version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"