mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 12:26:40 +07:00
add eslint-plugin-prettier
This commit is contained in:
parent
0e4d2bf44d
commit
ba0604ab9d
69 changed files with 419 additions and 249 deletions
20
.eslintrc.js
20
.eslintrc.js
|
@ -1,6 +1,7 @@
|
|||
module.exports = {
|
||||
extends: ['plugin:react/recommended', 'plugin:@next/next/recommended'],
|
||||
rules: {
|
||||
'prettier/prettier': 'error',
|
||||
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
|
||||
'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies
|
||||
'react/prop-types': 0,
|
||||
|
@ -9,13 +10,21 @@ module.exports = {
|
|||
'@next/next/no-img-element': 0,
|
||||
'unused-imports/no-unused-imports': 'warn',
|
||||
// 'no-unused-vars': 'warn',
|
||||
'quotes': [2, 'single', { 'avoidEscape': true }],
|
||||
quotes: [2, 'single', { avoidEscape: true }],
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
alphabetize: { order: 'asc' },
|
||||
'newlines-between': 'always',
|
||||
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'unknown'],
|
||||
groups: [
|
||||
'builtin',
|
||||
'external',
|
||||
'internal',
|
||||
'parent',
|
||||
'sibling',
|
||||
'index',
|
||||
'unknown',
|
||||
],
|
||||
pathGroups: [
|
||||
{
|
||||
pattern: 'react',
|
||||
|
@ -34,18 +43,17 @@ module.exports = {
|
|||
paths: [
|
||||
{
|
||||
name: 'ramda',
|
||||
message:
|
||||
'import from \'~/utils/ramda\' instead',
|
||||
message: "import from '~/utils/ramda' instead",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 7,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['import', 'react-hooks', 'unused-imports'],
|
||||
plugins: ['import', 'react-hooks', 'unused-imports', 'prettier'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
settings: {
|
||||
react: {
|
||||
|
|
|
@ -92,13 +92,14 @@
|
|||
"@typescript-eslint/parser": "^5.10.1",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-prettier": "^5.2.3",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-unused-imports": "^3.0.0",
|
||||
"husky": "^7.0.4",
|
||||
"lint-staged": "^12.1.6",
|
||||
"next-transpile-modules": "^9.0.0",
|
||||
"prettier": "^2.7.1"
|
||||
"prettier": "^3.0.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"./**/*.{js,jsx,ts,tsx}": [
|
||||
|
|
|
@ -14,7 +14,7 @@ interface Props extends DivProps {
|
|||
username?: string;
|
||||
size?: number;
|
||||
hasUpdates?: boolean;
|
||||
preset?: typeof imagePresets[keyof typeof imagePresets];
|
||||
preset?: (typeof imagePresets)[keyof typeof imagePresets];
|
||||
}
|
||||
|
||||
const Avatar = forwardRef<HTMLDivElement, Props>(
|
||||
|
|
|
@ -20,7 +20,7 @@ export const COMMENT_BLOCK_DETECTORS = [
|
|||
];
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
export enum SidebarName {
|
||||
Settings = 'settings',
|
||||
Tag = 'tag',
|
||||
|
|
|
@ -17,7 +17,7 @@ export const themeColors: Record<Theme, ThemeColors> = {
|
|||
'linear-gradient(165deg, #ff7549 -50%, #ff3344 150%)',
|
||||
'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]: {
|
||||
name: 'Веспера',
|
||||
|
|
|
@ -37,7 +37,7 @@ export const imagePresets = {
|
|||
flow_horizontal: 'flow_horizontal',
|
||||
} 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>> = {
|
||||
[imagePresets[1600]]: 1600,
|
||||
|
@ -49,7 +49,7 @@ export const imageSrcSets: Partial<Record<ImagePreset, number>> = {
|
|||
|
||||
export const flowDisplayToPreset: Record<
|
||||
FlowDisplayVariant,
|
||||
typeof imagePresets[keyof typeof imagePresets]
|
||||
(typeof imagePresets)[keyof typeof imagePresets]
|
||||
> = {
|
||||
single: 'flow_square',
|
||||
quadro: 'flow_square',
|
||||
|
|
|
@ -3,10 +3,12 @@ import dynamic from 'next/dynamic';
|
|||
import type { BorisSuperpowersProps } from './index';
|
||||
|
||||
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,
|
||||
loading: () => <div />,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
@ -8,7 +8,7 @@ import { DialogComponentProps } from '~/types/modal';
|
|||
import { values } from '~/utils/ramda';
|
||||
|
||||
export interface EditorCreateDialogProps extends DialogComponentProps {
|
||||
type: typeof NODE_TYPES[keyof typeof NODE_TYPES];
|
||||
type: (typeof NODE_TYPES)[keyof typeof NODE_TYPES];
|
||||
isInLab: boolean;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import { TextEditor } from '../components/TextEditor';
|
|||
import { VideoEditor } from '../components/VideoEditor';
|
||||
|
||||
export const NODE_EDITORS: Record<
|
||||
typeof NODE_TYPES[keyof typeof NODE_TYPES],
|
||||
(typeof NODE_TYPES)[keyof typeof NODE_TYPES],
|
||||
FC<NodeEditorProps>
|
||||
> = {
|
||||
[NODE_TYPES.IMAGE]: ImageEditor,
|
||||
|
@ -22,7 +22,7 @@ export const NODE_EDITORS: Record<
|
|||
};
|
||||
|
||||
export const NODE_EDITOR_DATA: Record<
|
||||
typeof NODE_TYPES[keyof typeof NODE_TYPES],
|
||||
(typeof NODE_TYPES)[keyof typeof NODE_TYPES],
|
||||
Partial<INode>
|
||||
> = {
|
||||
[NODE_TYPES.TEXT]: {
|
||||
|
|
|
@ -4,9 +4,12 @@ import type { HeaderProps } from '~/containers/main/Header/index';
|
|||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
export const HeaderSSR = dynamic<HeaderProps>(() => import('./index').then(it => it.Header), {
|
||||
ssr: false,
|
||||
loading: () => <div className={styles.wrap} />,
|
||||
});
|
||||
export const HeaderSSR = dynamic<HeaderProps>(
|
||||
() => import('./index').then((it) => it.Header),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => <div className={styles.wrap} />,
|
||||
},
|
||||
);
|
||||
|
||||
export const HeaderSSRPlaceholder = () => <div className={styles.wrap} />;
|
||||
|
|
|
@ -3,5 +3,6 @@ import dynamic from 'next/dynamic';
|
|||
import type { SubmitBarProps } from './index';
|
||||
|
||||
export const SubmitBarSSR = dynamic<SubmitBarProps>(
|
||||
() => import('./index').then(it => it.SubmitBar),
|
||||
{ ssr: false });
|
||||
() => import('./index').then((it) => it.SubmitBar),
|
||||
{ ssr: false },
|
||||
);
|
||||
|
|
|
@ -14,7 +14,7 @@ import type { SidebarComponentProps } from '~/types/sidebar';
|
|||
import { isNil } from '~/utils/ramda';
|
||||
|
||||
const tabs = ['profile', 'notifications', 'bookmarks'] as const;
|
||||
type TabName = typeof tabs[number];
|
||||
type TabName = (typeof tabs)[number];
|
||||
|
||||
interface SettingsSidebarProps
|
||||
extends SidebarComponentProps<SidebarName.Settings> {
|
||||
|
|
|
@ -10,7 +10,7 @@ export const useLastSeenBoris = () => {
|
|||
async (date: string) => {
|
||||
await update({ last_seen_boris: date }, false);
|
||||
},
|
||||
[update]
|
||||
[update],
|
||||
);
|
||||
|
||||
return { setLastSeen, lastSeen };
|
||||
|
|
|
@ -20,7 +20,7 @@ export const useLoginLogoutRestore = () => {
|
|||
showToastInfo(getRandomPhrase('WELCOME'));
|
||||
return result.user;
|
||||
},
|
||||
[auth]
|
||||
[auth],
|
||||
);
|
||||
|
||||
return { logout, login };
|
||||
|
|
|
@ -5,8 +5,9 @@ import { API } from '~/constants/api';
|
|||
import { getErrorMessage } from '~/utils/errors/getErrorMessage';
|
||||
|
||||
export const useRestoreCode = (code: string) => {
|
||||
const { data, isValidating, error } = useSWR(API.USER.REQUEST_CODE(code), () =>
|
||||
apiCheckRestoreCode({ code })
|
||||
const { data, isValidating, error } = useSWR(
|
||||
API.USER.REQUEST_CODE(code),
|
||||
() => apiCheckRestoreCode({ code }),
|
||||
);
|
||||
|
||||
const codeUser = data?.user;
|
||||
|
|
|
@ -18,7 +18,7 @@ const validationSchema = object({
|
|||
.test(
|
||||
'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 = (
|
||||
code: string,
|
||||
fetcher: (props: { code: string; password: string }) => Promise<{ token: string; user: IUser }>,
|
||||
onSuccess: () => void
|
||||
fetcher: (props: {
|
||||
code: string;
|
||||
password: string;
|
||||
}) => Promise<{ token: string; user: IUser }>,
|
||||
onSuccess: () => void,
|
||||
) => {
|
||||
const auth = useAuthStore();
|
||||
|
||||
const onSubmit = useCallback<FormikConfig<RestorePasswordData>['onSubmit']>(
|
||||
async (values, { setErrors }) => {
|
||||
try {
|
||||
const { token, user } = await fetcher({ password: values.newPassword, code });
|
||||
const { token, user } = await fetcher({
|
||||
password: values.newPassword,
|
||||
code,
|
||||
});
|
||||
auth.setUser(user);
|
||||
auth.setToken(token);
|
||||
onSuccess();
|
||||
|
@ -47,7 +53,7 @@ export const useRestorePasswordForm = (
|
|||
}
|
||||
}
|
||||
},
|
||||
[onSuccess, fetcher, code, auth]
|
||||
[onSuccess, fetcher, code, auth],
|
||||
);
|
||||
|
||||
return useFormik<RestorePasswordData>({
|
||||
|
|
|
@ -15,7 +15,7 @@ type RestoreRequestData = Asserts<typeof validationSchema>;
|
|||
|
||||
export const useRestoreRequestForm = (
|
||||
fetcher: (field: string) => Promise<unknown>,
|
||||
onSuccess: () => void
|
||||
onSuccess: () => void,
|
||||
) => {
|
||||
const onSubmit = useCallback<FormikConfig<RestoreRequestData>['onSubmit']>(
|
||||
async (values, { setErrors }) => {
|
||||
|
@ -31,7 +31,7 @@ export const useRestoreRequestForm = (
|
|||
}
|
||||
}
|
||||
},
|
||||
[fetcher, onSuccess]
|
||||
[fetcher, onSuccess],
|
||||
);
|
||||
|
||||
return useFormik({
|
||||
|
|
|
@ -13,6 +13,6 @@ export const useSessionCookie = () => {
|
|||
autorun(() => {
|
||||
setCookie('session', auth.token, 30);
|
||||
}),
|
||||
[auth]
|
||||
[auth],
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,9 +9,7 @@ import { showErrorToast } from '~/utils/errors/showToast';
|
|||
|
||||
const validationSchema = object({
|
||||
username: string().required(ERRORS.REQUIRED),
|
||||
password: string()
|
||||
.required(ERRORS.REQUIRED)
|
||||
.min(6, ERRORS.PASSWORD_IS_SHORT),
|
||||
password: string().required(ERRORS.REQUIRED).min(6, ERRORS.PASSWORD_IS_SHORT),
|
||||
});
|
||||
|
||||
type SocialRegisterData = Asserts<typeof validationSchema>;
|
||||
|
@ -23,7 +21,7 @@ export const useSocialRegisterForm = (
|
|||
username: string;
|
||||
password: string;
|
||||
}) => Promise<{ token: string }>,
|
||||
onSuccess: (token: string) => void
|
||||
onSuccess: (token: string) => void,
|
||||
) => {
|
||||
const onSubmit = useCallback<FormikConfig<SocialRegisterData>['onSubmit']>(
|
||||
async (values, { setErrors }) => {
|
||||
|
@ -43,7 +41,7 @@ export const useSocialRegisterForm = (
|
|||
}
|
||||
}
|
||||
},
|
||||
[token, onSuccess, fetcher]
|
||||
[token, onSuccess, fetcher],
|
||||
);
|
||||
|
||||
return useFormik<SocialRegisterData>({
|
||||
|
|
|
@ -7,7 +7,10 @@ const today = new Date();
|
|||
export const useUserActiveStatus = (lastSeen?: string) => {
|
||||
try {
|
||||
const lastSeenDate = lastSeen ? parseISO(lastSeen) : undefined;
|
||||
return lastSeenDate && differenceInDays(today, lastSeenDate) < INACTIVE_ACCOUNT_DAYS;
|
||||
return (
|
||||
lastSeenDate &&
|
||||
differenceInDays(today, lastSeenDate) < INACTIVE_ACCOUNT_DAYS
|
||||
);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -6,12 +6,14 @@ import { initialBackendStats } from '~/constants/boris/constants';
|
|||
import { BorisUsageStats } from '~/types/boris';
|
||||
|
||||
export const useBorisStats = () => {
|
||||
const { data: backend = initialBackendStats, isValidating: isValidatingBackend } = useSWR(
|
||||
API.BORIS.GET_BACKEND_STATS,
|
||||
() => getBorisBackendStats()
|
||||
);
|
||||
const {
|
||||
data: backend = initialBackendStats,
|
||||
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 = {
|
||||
backend,
|
||||
|
|
|
@ -3,9 +3,16 @@ import { useMemo } from 'react';
|
|||
import { normalizeBrightColor } from '~/utils/color';
|
||||
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(
|
||||
() => (val && normalizeBrightColor(stringToColour(val), saturation, lightness)) || '',
|
||||
[lightness, saturation, val]
|
||||
() =>
|
||||
(val &&
|
||||
normalizeBrightColor(stringToColour(val), saturation, lightness)) ||
|
||||
'',
|
||||
[lightness, saturation, val],
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@ export const useColorGradientFromString = (
|
|||
val?: string,
|
||||
saturation = 3,
|
||||
lightness = 3,
|
||||
angle = 155
|
||||
angle = 155,
|
||||
) =>
|
||||
useMemo(() => {
|
||||
if (!val) {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
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(() => {
|
||||
try {
|
||||
return localStorage.getItem(`vault_${key}`) || initial;
|
||||
|
|
|
@ -4,15 +4,18 @@ export const useFocusEvent = (initialState = false, delay = 0) => {
|
|||
const [focused, setFocused] = useState(initialState);
|
||||
|
||||
const onFocus = useCallback(
|
||||
event => {
|
||||
(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
setFocused(true);
|
||||
},
|
||||
[setFocused]
|
||||
[setFocused],
|
||||
);
|
||||
const onBlur = useCallback(
|
||||
() => setTimeout(() => setFocused(false), delay),
|
||||
[delay],
|
||||
);
|
||||
const onBlur = useCallback(() => setTimeout(() => setFocused(false), delay), [delay]);
|
||||
|
||||
return { focused, onBlur, onFocus };
|
||||
};
|
||||
|
|
|
@ -7,12 +7,13 @@ export const useFormatWrapper = (onChange: (val: string) => void) => {
|
|||
target: HTMLTextAreaElement,
|
||||
|
||||
prefix = '',
|
||||
suffix = ''
|
||||
) => event => {
|
||||
event.preventDefault();
|
||||
wrapTextInsideInput(target, prefix, suffix, onChange);
|
||||
},
|
||||
[onChange]
|
||||
suffix = '',
|
||||
) =>
|
||||
(event) => {
|
||||
event.preventDefault();
|
||||
wrapTextInsideInput(target, prefix, suffix, onChange);
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -21,7 +22,7 @@ export const wrapTextInsideInput = (
|
|||
target: HTMLTextAreaElement,
|
||||
prefix: string,
|
||||
suffix: string,
|
||||
onChange: (val: string) => void
|
||||
onChange: (val: string) => void,
|
||||
) => {
|
||||
if (!target) return;
|
||||
|
||||
|
@ -34,7 +35,7 @@ export const wrapTextInsideInput = (
|
|||
onChange(
|
||||
target.value.substring(0, start) +
|
||||
replacement +
|
||||
target.value.substring(end, target.value.length)
|
||||
target.value.substring(end, target.value.length),
|
||||
);
|
||||
|
||||
target.focus();
|
||||
|
|
|
@ -2,7 +2,8 @@ import { useCallback, useEffect } from 'react';
|
|||
|
||||
export const useInfiniteLoader = (loader: () => void, isLoading?: boolean) => {
|
||||
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;
|
||||
|
||||
|
|
|
@ -5,13 +5,13 @@ import { getImageFromPaste } from '~/utils/uploader';
|
|||
// useInputPasteUpload attaches event listener to input, that calls onUpload if user pasted any image
|
||||
export const useInputPasteUpload = (onUpload: (files: File[]) => void) => {
|
||||
return useCallback(
|
||||
async event => {
|
||||
async (event) => {
|
||||
const image = await getImageFromPaste(event);
|
||||
|
||||
if (!image) return;
|
||||
|
||||
onUpload([image]);
|
||||
},
|
||||
[onUpload]
|
||||
[onUpload],
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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(
|
||||
() =>
|
||||
[
|
||||
|
@ -35,5 +39,5 @@ export const usePopperModifiers = (offsetX = 0, offsetY = 10, justify?: boolean)
|
|||
},
|
||||
...(justify ? [sameWidth] : []),
|
||||
] as Modifier<any>[],
|
||||
[offsetX, offsetY, justify]
|
||||
[offsetX, offsetY, justify],
|
||||
);
|
||||
|
|
|
@ -11,7 +11,7 @@ const getHeight = () => {
|
|||
body.offsetHeight,
|
||||
html.clientHeight,
|
||||
html.scrollHeight,
|
||||
html.offsetHeight
|
||||
html.offsetHeight,
|
||||
);
|
||||
};
|
||||
export const useScrollHeight = () => getHeight();
|
||||
|
|
|
@ -18,6 +18,6 @@ export const useScrollToTop = (deps?: any[]) => {
|
|||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
deps && Array.isArray(deps) ? deps : []
|
||||
deps && Array.isArray(deps) ? deps : [],
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const useScrollTop = () => {
|
||||
const [top, setTop] = useState(typeof window !== 'undefined' ? window.scrollY : 0);
|
||||
const [top, setTop] = useState(
|
||||
typeof window !== 'undefined' ? window.scrollY : 0,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setTop(window.scrollY);
|
||||
|
|
|
@ -6,11 +6,12 @@ export const useFlowCellControls = (
|
|||
id: INode['id'],
|
||||
description: string | undefined,
|
||||
flow: FlowDisplay,
|
||||
onChangeCellView: (id: INode['id'], flow: FlowDisplay) => void
|
||||
onChangeCellView: (id: INode['id'], flow: FlowDisplay) => void,
|
||||
) => {
|
||||
const onChange = useCallback(
|
||||
(value: Partial<FlowDisplay>) => onChangeCellView(id, { ...flow, ...value }),
|
||||
[flow, id, onChangeCellView]
|
||||
(value: Partial<FlowDisplay>) =>
|
||||
onChangeCellView(id, { ...flow, ...value }),
|
||||
[flow, id, onChangeCellView],
|
||||
);
|
||||
|
||||
const hasDescription = !!description && description.length > 32;
|
||||
|
|
|
@ -17,6 +17,6 @@ export const useFlowSetCellView = () => {
|
|||
showErrorToast(error);
|
||||
}
|
||||
},
|
||||
[updateNode]
|
||||
[updateNode],
|
||||
);
|
||||
};
|
||||
|
|
|
@ -21,15 +21,19 @@ export const useGetLabStats = () => {
|
|||
heroes: lab.heroes,
|
||||
tags: lab.tags,
|
||||
},
|
||||
onSuccess: data => {
|
||||
onSuccess: (data) => {
|
||||
lab.setHeroes(data.heroes);
|
||||
lab.setTags(data.tags);
|
||||
},
|
||||
refreshInterval,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const { data: updatesData, isValidating: isValidatingUpdates, mutate: mutateUpdates } = useSWR(
|
||||
const {
|
||||
data: updatesData,
|
||||
isValidating: isValidatingUpdates,
|
||||
mutate: mutateUpdates,
|
||||
} = useSWR(
|
||||
isUser ? API.LAB.UPDATES : null,
|
||||
async () => {
|
||||
const result = await getLabUpdates();
|
||||
|
@ -37,26 +41,27 @@ export const useGetLabStats = () => {
|
|||
},
|
||||
{
|
||||
fallbackData: lab.updates,
|
||||
onSuccess: data => {
|
||||
onSuccess: (data) => {
|
||||
lab.setUpdates(data);
|
||||
},
|
||||
refreshInterval,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const heroes = useMemo(() => stats?.heroes || [], [stats]);
|
||||
const tags = useMemo(() => stats?.tags || [], [stats]);
|
||||
const updates = useMemo(() => updatesData || [], [updatesData]);
|
||||
|
||||
const isLoading = (!stats || !updates) && (isValidatingStats || isValidatingUpdates);
|
||||
const isLoading =
|
||||
(!stats || !updates) && (isValidatingStats || isValidatingUpdates);
|
||||
const seenNode = useCallback(
|
||||
async (nodeId: number) => {
|
||||
await mutateUpdates(
|
||||
updates.filter(it => it.id !== nodeId),
|
||||
false
|
||||
updates.filter((it) => it.id !== nodeId),
|
||||
false,
|
||||
);
|
||||
},
|
||||
[mutateUpdates, updates]
|
||||
[mutateUpdates, updates],
|
||||
);
|
||||
|
||||
return { heroes, tags, updates, isLoading, seenNode };
|
||||
|
|
|
@ -11,7 +11,7 @@ const getKey = (username: string): string | null => {
|
|||
};
|
||||
export const useMessages = (username: string) => {
|
||||
const { data, isValidating } = useSWR(getKey(username), async () =>
|
||||
apiGetUserMessages({ username })
|
||||
apiGetUserMessages({ username }),
|
||||
);
|
||||
|
||||
const messages: IMessage[] = useMemo(() => data?.messages || [], [data]);
|
||||
|
|
|
@ -5,7 +5,9 @@ import { useModalStore } from '~/store/modal/useModalStore';
|
|||
import { DialogComponentProps } from '~/types/modal';
|
||||
|
||||
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
|
||||
? keyof Omit<U, 'onRequestClose' | 'children'> extends never
|
||||
? {}
|
||||
|
@ -21,7 +23,7 @@ export const useModal = () => {
|
|||
<T extends Dialog>(dialog: T, props: DialogContentProps[T]) => {
|
||||
setCurrent(dialog, props);
|
||||
},
|
||||
[setCurrent]
|
||||
[setCurrent],
|
||||
);
|
||||
|
||||
return { showModal, hideModal: hide, current, isOpened: !!current };
|
||||
|
|
|
@ -10,6 +10,6 @@ export const useShowModal = <T extends Dialog>(dialog: T) => {
|
|||
(props: DialogContentProps[T]) => {
|
||||
modal.showModal(dialog, props);
|
||||
},
|
||||
[dialog, modal]
|
||||
[dialog, modal],
|
||||
);
|
||||
};
|
||||
|
|
|
@ -11,6 +11,6 @@ export const useImageModal = () => {
|
|||
(images: IFile[], index: number) => {
|
||||
showModal({ items: images, index });
|
||||
},
|
||||
[showModal]
|
||||
[showModal],
|
||||
);
|
||||
};
|
||||
|
|
|
@ -17,7 +17,7 @@ export const useNavigation = () => {
|
|||
craHistory.push(url);
|
||||
}
|
||||
},
|
||||
[craHistory, nextRouter]
|
||||
[craHistory, nextRouter],
|
||||
);
|
||||
|
||||
return { push };
|
||||
|
|
|
@ -16,9 +16,13 @@ export const useCreateNode = () => {
|
|||
if (node.is_promoted) {
|
||||
flow.setNodes([result.node, ...flow.nodes]);
|
||||
} 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],
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,13 +6,13 @@ import { groupCommentsByUser } from '~/utils/fn';
|
|||
export const useGrouppedComments = (
|
||||
comments: IComment[],
|
||||
order: 'ASC' | 'DESC',
|
||||
lastSeen?: string
|
||||
lastSeen?: string,
|
||||
) =>
|
||||
useMemo(
|
||||
() =>
|
||||
(order === 'DESC' ? [...comments].reverse() : comments).reduce(
|
||||
groupCommentsByUser(lastSeen),
|
||||
[]
|
||||
[],
|
||||
),
|
||||
[comments, lastSeen, order]
|
||||
[comments, lastSeen, order],
|
||||
);
|
||||
|
|
|
@ -6,7 +6,10 @@ import { useModal } from '~/hooks/modal/useModal';
|
|||
import { INode } from '~/types';
|
||||
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 onLike = useCallback(async () => {
|
||||
|
@ -35,17 +38,20 @@ export const useNodeActions = (node: INode, update: (node: Partial<INode>) => Pr
|
|||
|
||||
const onLock = useCallback(async () => {
|
||||
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 });
|
||||
} catch (error) {
|
||||
showErrorToast(error);
|
||||
}
|
||||
}, [node.deleted_at, node.id, update]);
|
||||
|
||||
const onEdit = useCallback(() => showModal(Dialog.EditNode, { nodeId: node.id! }), [
|
||||
node,
|
||||
showModal,
|
||||
]);
|
||||
const onEdit = useCallback(
|
||||
() => showModal(Dialog.EditNode, { nodeId: node.id! }),
|
||||
[node, showModal],
|
||||
);
|
||||
|
||||
return { onLike, onStar, onLock, onEdit };
|
||||
};
|
||||
|
|
|
@ -4,7 +4,8 @@ import { UploadType } from '~/constants/uploads';
|
|||
import { INode } from '~/types';
|
||||
|
||||
export const useNodeAudios = (node: INode) => {
|
||||
return useMemo(() => node.files.filter(file => file && file.type === UploadType.Audio), [
|
||||
node.files,
|
||||
]);
|
||||
return useMemo(
|
||||
() => node.files.filter((file) => file && file.type === UploadType.Audio),
|
||||
[node.files],
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import { useCallback, useRef } from 'react';
|
||||
|
||||
import { FormikConfig, FormikHelpers, useFormik, useFormikContext } from 'formik';
|
||||
import {
|
||||
FormikConfig,
|
||||
FormikHelpers,
|
||||
useFormik,
|
||||
useFormikContext,
|
||||
} from 'formik';
|
||||
import { object } from 'yup';
|
||||
|
||||
import { INode } from '~/types';
|
||||
|
@ -10,31 +15,31 @@ import { showErrorToast } from '~/utils/errors/showToast';
|
|||
|
||||
const validationSchema = object().shape({});
|
||||
|
||||
const afterSubmit = ({ resetForm, setSubmitting, setErrors }: FormikHelpers<INode>) => (
|
||||
error?: unknown
|
||||
) => {
|
||||
setSubmitting(false);
|
||||
const afterSubmit =
|
||||
({ resetForm, setSubmitting, setErrors }: FormikHelpers<INode>) =>
|
||||
(error?: unknown) => {
|
||||
setSubmitting(false);
|
||||
|
||||
if (error) {
|
||||
showErrorToast(error);
|
||||
return;
|
||||
}
|
||||
if (error) {
|
||||
showErrorToast(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (getValidationErrors(error)) {
|
||||
setErrors(getValidationErrors(error)!);
|
||||
return;
|
||||
}
|
||||
if (getValidationErrors(error)) {
|
||||
setErrors(getValidationErrors(error)!);
|
||||
return;
|
||||
}
|
||||
|
||||
if (resetForm) {
|
||||
resetForm();
|
||||
}
|
||||
};
|
||||
if (resetForm) {
|
||||
resetForm();
|
||||
}
|
||||
};
|
||||
|
||||
export const useNodeFormFormik = (
|
||||
values: INode,
|
||||
uploader: Uploader,
|
||||
stopEditing: () => void,
|
||||
sendSaveRequest: (node: INode) => Promise<unknown>
|
||||
sendSaveRequest: (node: INode) => Promise<unknown>,
|
||||
) => {
|
||||
const { current: initialValues } = useRef(values);
|
||||
|
||||
|
@ -53,7 +58,7 @@ export const useNodeFormFormik = (
|
|||
afterSubmit(helpers)(error);
|
||||
}
|
||||
},
|
||||
[sendSaveRequest, uploader.files]
|
||||
[sendSaveRequest, uploader.files],
|
||||
);
|
||||
|
||||
return useFormik<INode>({
|
||||
|
|
|
@ -4,7 +4,8 @@ import { UploadType } from '~/constants/uploads';
|
|||
import { INode } from '~/types';
|
||||
|
||||
export const useNodeImages = (node: INode) => {
|
||||
return useMemo(() => node.files.filter(file => file && file.type === UploadType.Image), [
|
||||
node.files,
|
||||
]);
|
||||
return useMemo(
|
||||
() => node.files.filter((file) => file && file.type === UploadType.Image),
|
||||
[node.files],
|
||||
);
|
||||
};
|
||||
|
|
|
@ -27,6 +27,6 @@ export const useUpdateNode = (id: number) => {
|
|||
await lab.updateNode(result.node.id!, result.node);
|
||||
}
|
||||
},
|
||||
[update, flow, lab]
|
||||
[update, flow, lab],
|
||||
);
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@ export const useGetProfile = (username?: string) => {
|
|||
},
|
||||
{
|
||||
refreshInterval: 60000,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const profile = data || EMPTY_USER;
|
||||
|
@ -29,7 +29,7 @@ export const useGetProfile = (username?: string) => {
|
|||
async (user: Partial<IUser>) => {
|
||||
await mutate({ ...profile, ...user });
|
||||
},
|
||||
[mutate, profile]
|
||||
[mutate, profile],
|
||||
);
|
||||
|
||||
return { profile, isLoading: !data && isValidating, update };
|
||||
|
|
|
@ -9,21 +9,19 @@ import { flatten } from '~/utils/ramda';
|
|||
|
||||
const RESULTS_COUNT = 20;
|
||||
|
||||
const getKey: (text: string) => SWRInfiniteKeyLoader = text => (
|
||||
pageIndex,
|
||||
previousPageData: INode[]
|
||||
) => {
|
||||
if ((pageIndex > 0 && !previousPageData?.length) || !text) return null;
|
||||
const getKey: (text: string) => SWRInfiniteKeyLoader =
|
||||
(text) => (pageIndex, previousPageData: INode[]) => {
|
||||
if ((pageIndex > 0 && !previousPageData?.length) || !text) return null;
|
||||
|
||||
const props: GetSearchResultsRequest = {
|
||||
text,
|
||||
skip: pageIndex * RESULTS_COUNT,
|
||||
take: RESULTS_COUNT,
|
||||
const props: GetSearchResultsRequest = {
|
||||
text,
|
||||
skip: pageIndex * RESULTS_COUNT,
|
||||
take: RESULTS_COUNT,
|
||||
};
|
||||
|
||||
return JSON.stringify(props);
|
||||
};
|
||||
|
||||
return JSON.stringify(props);
|
||||
};
|
||||
|
||||
export const useSearch = () => {
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [debouncedSearchText, setDebouncedSearchText] = useState('');
|
||||
|
@ -40,7 +38,7 @@ export const useSearch = () => {
|
|||
const result = await getSearchResults(props);
|
||||
|
||||
return result.nodes;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const loadMore = useCallback(() => setSize(size + 1), [setSize, size]);
|
||||
|
|
|
@ -26,5 +26,5 @@ export const useTagAutocomplete = (
|
|||
},
|
||||
);
|
||||
|
||||
return useMemo(() => (search ? data ?? [] : []), [data, search]);
|
||||
return useMemo(() => (search ? (data ?? []) : []), [data, search]);
|
||||
};
|
||||
|
|
|
@ -9,13 +9,11 @@ import { flatten, isNil } from '~/utils/ramda';
|
|||
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
const getKey: (tag: string) => SWRInfiniteKeyLoader = tag => (
|
||||
pageIndex,
|
||||
previousPageData: INode[]
|
||||
) => {
|
||||
if (pageIndex > 0 && !previousPageData?.length) return null;
|
||||
return `${API.TAG.NODES}?tag=${tag}&page=${pageIndex}`;
|
||||
};
|
||||
const getKey: (tag: string) => SWRInfiniteKeyLoader =
|
||||
(tag) => (pageIndex, previousPageData: INode[]) => {
|
||||
if (pageIndex > 0 && !previousPageData?.length) return null;
|
||||
return `${API.TAG.NODES}?tag=${tag}&page=${pageIndex}`;
|
||||
};
|
||||
|
||||
const extractKey = (key: string) => {
|
||||
const re = new RegExp(`${API.TAG.NODES}\\?tag=[^&]+&page=(\\d+)`);
|
||||
|
@ -39,7 +37,7 @@ export const useTagNodes = (tag: string) => {
|
|||
});
|
||||
|
||||
return result.nodes;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const nodes = useMemo(() => flatten(data || []), [data]);
|
||||
|
@ -47,5 +45,12 @@ export const useTagNodes = (tag: string) => {
|
|||
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@ export const useUpdates = () => {
|
|||
const { data } = useSWR(
|
||||
isUser ? API.USER.GET_UPDATES : null,
|
||||
() => apiAuthGetUpdates({ exclude_dialogs: 0, last: '' }),
|
||||
{ refreshInterval: 5 * 60 * 1000 }
|
||||
{ refreshInterval: 5 * 60 * 1000 },
|
||||
);
|
||||
|
||||
const borisCommentedAt = data?.boris?.commented_at || '';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const reportWebVitals = onPerfEntry => {
|
||||
const reportWebVitals = (onPerfEntry) => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
|
|
|
@ -24,11 +24,13 @@ export class FlowStore {
|
|||
|
||||
/** removes node from updated after user seen it */
|
||||
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 */
|
||||
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)),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,7 +12,9 @@ export class MetadataStore {
|
|||
pending: string[] = [];
|
||||
|
||||
constructor(
|
||||
protected apiMetadataLoader: (ids: string[]) => Promise<Record<string, EmbedMetadata>>
|
||||
protected apiMetadataLoader: (
|
||||
ids: string[],
|
||||
) => Promise<Record<string, EmbedMetadata>>,
|
||||
) {
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
@ -59,7 +61,7 @@ export class MetadataStore {
|
|||
|
||||
try {
|
||||
const result = await this.apiMetadataLoader(items);
|
||||
const fetchedIDs = values(result).map(it => it.address);
|
||||
const fetchedIDs = values(result).map((it) => it.address);
|
||||
|
||||
runInAction(() => {
|
||||
this.pushMetadataItems(result);
|
||||
|
@ -72,7 +74,11 @@ export class MetadataStore {
|
|||
|
||||
/** adds items to queue */
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -153,13 +153,13 @@ export const NOTIFICATION_TYPES = {
|
|||
};
|
||||
|
||||
export type IMessageNotification = {
|
||||
type: typeof NOTIFICATION_TYPES['message'];
|
||||
type: (typeof NOTIFICATION_TYPES)['message'];
|
||||
content: Partial<IMessage>;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export type ICommentNotification = {
|
||||
type: typeof NOTIFICATION_TYPES['comment'];
|
||||
type: (typeof NOTIFICATION_TYPES)['comment'];
|
||||
content: Partial<IComment>;
|
||||
created_at: string;
|
||||
};
|
||||
|
|
|
@ -5,15 +5,14 @@ import type { SidebarComponents } from '~/constants/sidebar/components';
|
|||
export type SidebarComponent = keyof SidebarComponents;
|
||||
|
||||
// TODO: use it to store props for sidebar
|
||||
export type SidebarProps<
|
||||
T extends SidebarComponent
|
||||
> = SidebarComponents[T] extends FunctionComponent<infer U>
|
||||
? U extends object
|
||||
? U extends SidebarComponentProps<T>
|
||||
? Omit<U, keyof SidebarComponentProps<T>>
|
||||
export type SidebarProps<T extends SidebarComponent> =
|
||||
SidebarComponents[T] extends FunctionComponent<infer U>
|
||||
? U extends object
|
||||
? U extends SidebarComponentProps<T>
|
||||
? Omit<U, keyof SidebarComponentProps<T>>
|
||||
: U
|
||||
: U
|
||||
: U
|
||||
: {};
|
||||
: {};
|
||||
export interface SidebarComponentProps<T extends SidebarComponent> {
|
||||
onRequestClose: () => void;
|
||||
openSidebar: (name: T, props: SidebarProps<T>) => void;
|
||||
|
|
|
@ -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 { stringToColour } from '~/utils/dom';
|
||||
|
||||
export const normalizeBrightColor = (color?: string, saturationExp = 3, lightnessExp = 3) => {
|
||||
export const normalizeBrightColor = (
|
||||
color?: string,
|
||||
saturationExp = 3,
|
||||
lightnessExp = 3,
|
||||
) => {
|
||||
if (!color) {
|
||||
return '';
|
||||
}
|
||||
|
@ -12,12 +22,23 @@ export const normalizeBrightColor = (color?: string, saturationExp = 3, lightnes
|
|||
const saturation = hsla[1];
|
||||
const lightness = hsla[2];
|
||||
|
||||
const desaturated = saturationExp > 1 ? desaturate(color, saturation ** saturationExp) : color;
|
||||
return lightnessExp > 1 ? darken(desaturated, lightness ** lightnessExp) : desaturated;
|
||||
const 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) => {
|
||||
const color = normalizeBrightColor(stringToColour(val), saturation, lightness);
|
||||
export const generateColorTriplet = (
|
||||
val: string,
|
||||
saturation: number,
|
||||
lightness: number,
|
||||
) => {
|
||||
const color = normalizeBrightColor(
|
||||
stringToColour(val),
|
||||
saturation,
|
||||
lightness,
|
||||
);
|
||||
|
||||
return [
|
||||
color,
|
||||
|
@ -31,9 +52,13 @@ export const generateGradientFromColor = (
|
|||
saturation = 3,
|
||||
lightness = 3,
|
||||
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) {
|
||||
return it;
|
||||
}
|
||||
|
|
|
@ -6,5 +6,6 @@ export const CONFIG = {
|
|||
// image storage endpoint (sames as backend, but with /static usualy)
|
||||
remoteCurrent: process.env.NEXT_PUBLIC_REMOTE_CURRENT || '',
|
||||
// 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',
|
||||
};
|
||||
|
|
|
@ -3,11 +3,19 @@ import { differenceInDays, isAfter, isValid, parseISO } from 'date-fns';
|
|||
import { IComment, ICommentGroup } from '~/types';
|
||||
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) =>
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
|
@ -37,45 +45,49 @@ const getCommentDistance = (firstDate?: string, secondDate?: string) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const groupCommentsByUser = (lastSeen?: string) => (
|
||||
grouppedComments: ICommentGroup[],
|
||||
comment: IComment
|
||||
): ICommentGroup[] => {
|
||||
const last: ICommentGroup | undefined = path([grouppedComments.length - 1], grouppedComments);
|
||||
export const groupCommentsByUser =
|
||||
(lastSeen?: string) =>
|
||||
(grouppedComments: ICommentGroup[], comment: IComment): ICommentGroup[] => {
|
||||
const last: ICommentGroup | undefined = path(
|
||||
[grouppedComments.length - 1],
|
||||
grouppedComments,
|
||||
);
|
||||
|
||||
if (!comment.user) {
|
||||
return grouppedComments;
|
||||
}
|
||||
if (!comment.user) {
|
||||
return grouppedComments;
|
||||
}
|
||||
|
||||
return [
|
||||
...(!last || path(['user', 'id'], last) !== path(['user', 'id'], comment)
|
||||
? [
|
||||
// add new group
|
||||
...grouppedComments,
|
||||
{
|
||||
user: comment.user,
|
||||
comments: [comment],
|
||||
distancesInDays: [0],
|
||||
ids: [comment.id],
|
||||
hasNew: compareCommentDates(comment.created_at, lastSeen),
|
||||
},
|
||||
]
|
||||
: [
|
||||
// append to last group
|
||||
...grouppedComments.slice(0, grouppedComments.length - 1),
|
||||
{
|
||||
...last,
|
||||
distancesInDays: [
|
||||
...last.distancesInDays,
|
||||
getCommentDistance(
|
||||
comment?.created_at,
|
||||
last.comments[last.comments.length - 1]?.created_at
|
||||
),
|
||||
],
|
||||
comments: [...last.comments, comment],
|
||||
ids: [...last.ids, comment.id],
|
||||
hasNew: last.hasNew || compareCommentDates(comment.created_at, lastSeen),
|
||||
},
|
||||
]),
|
||||
];
|
||||
};
|
||||
return [
|
||||
...(!last || path(['user', 'id'], last) !== path(['user', 'id'], comment)
|
||||
? [
|
||||
// add new group
|
||||
...grouppedComments,
|
||||
{
|
||||
user: comment.user,
|
||||
comments: [comment],
|
||||
distancesInDays: [0],
|
||||
ids: [comment.id],
|
||||
hasNew: compareCommentDates(comment.created_at, lastSeen),
|
||||
},
|
||||
]
|
||||
: [
|
||||
// append to last group
|
||||
...grouppedComments.slice(0, grouppedComments.length - 1),
|
||||
{
|
||||
...last,
|
||||
distancesInDays: [
|
||||
...last.distancesInDays,
|
||||
getCommentDistance(
|
||||
comment?.created_at,
|
||||
last.comments[last.comments.length - 1]?.created_at,
|
||||
),
|
||||
],
|
||||
comments: [...last.comments, comment],
|
||||
ids: [...last.ids, comment.id],
|
||||
hasNew:
|
||||
last.hasNew ||
|
||||
compareCommentDates(comment.created_at, lastSeen),
|
||||
},
|
||||
]),
|
||||
];
|
||||
};
|
||||
|
|
|
@ -18,11 +18,16 @@ const ProfileContext = createContext<ProfileContextValue>({
|
|||
isLoading: false,
|
||||
});
|
||||
|
||||
export const ProfileProvider: FC<ProfileProviderProps> = ({ children, username }) => {
|
||||
export const ProfileProvider: FC<ProfileProviderProps> = ({
|
||||
children,
|
||||
username,
|
||||
}) => {
|
||||
const { profile, isLoading } = useGetProfile(username);
|
||||
|
||||
return (
|
||||
<ProfileContext.Provider value={{ profile, isLoading }}>{children}</ProfileContext.Provider>
|
||||
<ProfileContext.Provider value={{ profile, isLoading }}>
|
||||
{children}
|
||||
</ProfileContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -2,10 +2,12 @@ import { flatten, isEmpty } from '~/utils/ramda';
|
|||
|
||||
export const splitTextByYoutube = (strings: string[]): string[] =>
|
||||
flatten(
|
||||
strings.map(str =>
|
||||
str.split(/(https?:\/\/(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch)?(?:\?v=)?[\w\-&=]+)/)
|
||||
)
|
||||
strings.map((str) =>
|
||||
str.split(
|
||||
/(https?:\/\/(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch)?(?:\?v=)?[\w\-&=]+)/,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
export const splitTextOmitEmpty = (strings: string[]): string[] =>
|
||||
strings.map(el => el.trim()).filter(el => !isEmpty(el));
|
||||
strings.map((el) => el.trim()).filter((el) => !isEmpty(el));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/** just combines title elements to form title of the page */
|
||||
export const getPageTitle = (...props: string[]): string => {
|
||||
return ['Убежище', ...props].filter(it => it.trim()).join(' • ');
|
||||
return ['Убежище', ...props].filter((it) => it.trim()).join(' • ');
|
||||
};
|
||||
|
|
|
@ -3,11 +3,13 @@ import { ITag } from '~/types';
|
|||
export const separateTags = (tags: Partial<ITag>[]): Partial<ITag>[][] =>
|
||||
(tags || []).reduce(
|
||||
(obj, tag) =>
|
||||
tag?.title?.substr(0, 1) === '/' ? [[...obj[0], tag], obj[1]] : [obj[0], [...obj[1], tag]],
|
||||
[[], []] as Partial<ITag>[][]
|
||||
tag?.title?.substr(0, 1) === '/'
|
||||
? [[...obj[0], tag], obj[1]]
|
||||
: [obj[0], [...obj[1], tag]],
|
||||
[[], []] as Partial<ITag>[][],
|
||||
);
|
||||
|
||||
export const separateTagOptions = (options: string[]): string[][] =>
|
||||
separateTags(options.map((title): Partial<ITag> => ({ title }))).map(item =>
|
||||
item.filter(tag => tag.title).map(({ title }) => title!)
|
||||
separateTags(options.map((title): Partial<ITag> => ({ title }))).map((item) =>
|
||||
item.filter((tag) => tag.title).map(({ title }) => title!),
|
||||
);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { ERROR_LITERAL, ERRORS } from '~/constants/errors';
|
||||
import { ValueOf } from '~/types';
|
||||
|
||||
export const t = (string: ValueOf<typeof ERRORS>): ValueOf<typeof ERROR_LITERAL> =>
|
||||
ERROR_LITERAL[string] || string;
|
||||
export const t = (
|
||||
string: ValueOf<typeof ERRORS>,
|
||||
): ValueOf<typeof ERROR_LITERAL> => ERROR_LITERAL[string] || string;
|
||||
|
|
|
@ -2,10 +2,10 @@ import { FILE_MIMES, UploadType } from '~/constants/uploads';
|
|||
import { isMimeOfImage } from '~/utils/validators';
|
||||
|
||||
/** 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 '';
|
||||
|
||||
return new Promise<string>(resolve => {
|
||||
return new Promise<string>((resolve) => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => resolve(reader.result?.toString() || '');
|
||||
reader.readAsDataURL(file);
|
||||
|
@ -15,14 +15,17 @@ export const uploadGetThumb = async file => {
|
|||
/** returns UploadType by file */
|
||||
export const getFileType = (file: File): UploadType | undefined =>
|
||||
((file.type &&
|
||||
Object.keys(FILE_MIMES).find(mime => FILE_MIMES[mime].includes(file.type))) as UploadType) ||
|
||||
undefined;
|
||||
Object.keys(FILE_MIMES).find((mime) =>
|
||||
FILE_MIMES[mime].includes(file.type),
|
||||
)) as UploadType) || undefined;
|
||||
|
||||
/** 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;
|
||||
|
||||
return new Promise(resolve => {
|
||||
return new Promise((resolve) => {
|
||||
for (let index in items) {
|
||||
const item = items[index];
|
||||
|
||||
|
@ -31,7 +34,7 @@ export const getImageFromPaste = (event: ClipboardEvent): Promise<File | undefin
|
|||
const reader = new FileReader();
|
||||
const type = item.type;
|
||||
|
||||
reader.onload = function(e) {
|
||||
reader.onload = function (e) {
|
||||
if (!e.target?.result) {
|
||||
return;
|
||||
}
|
||||
|
@ -40,7 +43,7 @@ export const getImageFromPaste = (event: ClipboardEvent): Promise<File | undefin
|
|||
new File([e.target?.result], 'paste.png', {
|
||||
type,
|
||||
lastModified: new Date().getTime(),
|
||||
})
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
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;
|
||||
|
|
46
yarn.lock
46
yarn.lock
|
@ -221,6 +221,11 @@
|
|||
"@nodelib/fs.scandir" "2.1.5"
|
||||
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":
|
||||
version "1.0.0-next.21"
|
||||
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"
|
||||
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:
|
||||
version "4.6.0"
|
||||
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"
|
||||
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:
|
||||
version "1.3.2"
|
||||
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"
|
||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||
|
||||
prettier@^2.7.1:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64"
|
||||
integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==
|
||||
prettier-linter-helpers@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b"
|
||||
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:
|
||||
version "26.6.2"
|
||||
|
@ -3087,6 +3112,14 @@ swr@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/swr/-/swr-1.2.0.tgz#8649f6e9131ce94bbcf7ffd65c21334da3d1ec20"
|
||||
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:
|
||||
version "6.8.0"
|
||||
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"
|
||||
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:
|
||||
version "3.21.0"
|
||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue