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

@ -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>(

View file

@ -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;
};

View file

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

View file

@ -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: 'Веспера',

View file

@ -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',

View file

@ -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 />,
}
},
);

View file

@ -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;
}

View file

@ -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]: {

View file

@ -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} />;

View file

@ -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 },
);

View file

@ -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> {

View file

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

View file

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

View file

@ -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;

View file

@ -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>({

View file

@ -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({

View file

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

View file

@ -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>({

View file

@ -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;
}

View file

@ -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,

View file

@ -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],
);
};

View file

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

View file

@ -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;

View file

@ -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 };
};

View file

@ -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();

View file

@ -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;

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
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],
);
};

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

View file

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

View file

@ -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 : [],
);
};

View file

@ -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);

View file

@ -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;

View file

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

View file

@ -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 };

View file

@ -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]);

View file

@ -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 };

View file

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

View file

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

View file

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

View file

@ -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],
);
};

View file

@ -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],
);

View file

@ -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 };
};

View file

@ -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],
);
};

View file

@ -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>({

View file

@ -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],
);
};

View file

@ -27,6 +27,6 @@ export const useUpdateNode = (id: number) => {
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,
}
},
);
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 };

View file

@ -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]);

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 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,
};
};

View file

@ -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 || '';

View file

@ -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);

View file

@ -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)),
);
};
}

View file

@ -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;
}

View file

@ -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;
};

View file

@ -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;

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 { 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;
}

View file

@ -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',
};

View file

@ -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),
},
]),
];
};

View file

@ -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>
);
};

View file

@ -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));

View file

@ -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(' • ');
};

View file

@ -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!),
);

View file

@ -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;

View file

@ -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(),
})
}),
);
};

View file

@ -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;