From 0f700ad1d17f8aef2764788b7f80ece375b8bf10 Mon Sep 17 00:00:00 2001 From: Anton <36848395+antonmazhuto@users.noreply.github.com> Date: Wed, 10 Nov 2021 20:12:39 +0300 Subject: [PATCH] BOX-94 BOX-237 Add ability to add/remove card to favorites (#84) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * BOX-94 Add ability to add/remove card to favorites * BOX-94 Add ability to add/remove card to favorites * BOX-94 Add ability to add/remove card to favorites * BOX-94 Add ability to add/remove card to favorites * BOX-94 Add ability to add/remove card to favorites * BOX-94 Add ability to add/remove card to favorites * refactor(entities/card): remove currentCard * refactor(entities/card): remove unused $cards * fix(entities/card): favorites should work Co-authored-by: Антон Мажуто Co-authored-by: Sergey Sova --- openapi.config.js | 2 +- src/entities/card/model/index.ts | 69 +++++++++------ src/entities/card/organisms/card-list.tsx | 9 +- src/entities/card/organisms/card-preview.tsx | 88 +++++++++++++------- src/features/card/draft/model.ts | 5 +- src/pages/card/edit/model.ts | 30 +++++-- src/pages/card/view/model.ts | 18 ++++ src/pages/card/view/page.tsx | 9 +- src/pages/home/model.ts | 28 +++++-- src/pages/home/page.tsx | 2 +- src/pages/search/model.ts | 25 +++++- src/pages/user/model.ts | 32 ++++--- src/pages/user/page.tsx | 7 +- src/shared/api/internal/index.gen.ts | 75 +++++++++++++++++ src/shared/api/types.ts | 8 +- 15 files changed, 298 insertions(+), 109 deletions(-) diff --git a/openapi.config.js b/openapi.config.js index 733efe8c..6df9686c 100644 --- a/openapi.config.js +++ b/openapi.config.js @@ -1,7 +1,7 @@ module.exports = { file: 'https://cardbox.github.io/backend/api-internal/openapi.yaml', templateFileNameCode: 'index.gen.ts', - outputDir: './src/api/internal', + outputDir: './src/shared/api/internal', presets: [ [ 'effector-openapi-preset', diff --git a/src/entities/card/model/index.ts b/src/entities/card/model/index.ts index a2888cfd..87121ad7 100644 --- a/src/entities/card/model/index.ts +++ b/src/entities/card/model/index.ts @@ -1,40 +1,61 @@ -import type { Card } from '@box/shared/api'; -import { createEvent, createStore } from 'effector'; -import { internalApi } from '@box/shared/api'; - -export const setCards = createEvent(); +import { Card, internalApi } from '@box/shared/api'; +import { attach, combine, createEvent, createStore, sample } from 'effector'; export const $cardsCache = createStore<{ cache: Record }>({ cache: {}, }); -export const $cards = createStore([]); -export const $currentCard = createStore(null); -// TODO: remove current card id and current card store from entities -export const $currentCardId = $currentCard.map((card) => - card ? card.id : null, -); +export const cardsSaveFx = attach({ effect: internalApi.cardsSave }); +export const cardsUnsaveFx = attach({ effect: internalApi.cardsUnsave }); -$cards.on(internalApi.cardsList.done, (cards, { params, result }) => - !params.body?.favorites ? (result.answer.cards as Card[]) : cards, -); +// @TODO It's bad practice to use global store. Will be fixed after BOX-250 +export const $favoritesIds = createStore([]); -$cards.on(setCards, (_, cards) => cards); +export const changeFavorites = createEvent(); +changeFavorites.watch((list) => console.info('————', list)); +export const $favoritesCards = combine( + $favoritesIds, + $cardsCache, + (ids, { cache }) => ids.map((id) => cache[id] ?? null), +); -$currentCard.on(internalApi.cardsGet.doneData, (_, { answer }) => answer.card); +export const favoritesAdd = createEvent(); +export const favoritesRemove = createEvent(); $cardsCache .on(internalApi.cardsList.doneData, (cache, { answer }) => updateCache(cache, answer.cards as Card[]), ) - .on(internalApi.cardsGet.doneData, (cache, { answer }) => - updateCache(cache, [answer.card as Card]), - ) - .on(internalApi.cardsCreate.doneData, (cache, { answer }) => - updateCache(cache, [answer.card as Card]), - ) - .on(internalApi.cardsEdit.doneData, (cache, { answer }) => - updateCache(cache, [answer.card as Card]), + .on( + [ + internalApi.cardsGet.doneData, + internalApi.cardsCreate.doneData, + internalApi.cardsEdit.doneData, + internalApi.cardsSave.doneData, + internalApi.cardsUnsave.doneData, + ], + (cache, { answer }) => updateCache(cache, [answer.card as Card]), + ); + +sample({ + clock: favoritesAdd, + fn: (cardId) => ({ body: { cardId } }), + target: cardsSaveFx, +}); + +sample({ + clock: favoritesRemove, + fn: (cardId) => ({ + body: { cardId }, + }), + target: cardsUnsaveFx, +}); + +$favoritesIds + .on(changeFavorites, (_, ids) => ids) + .on(cardsSaveFx.doneData, (ids, { answer }) => [...ids, answer.card.id]) + .on(cardsUnsaveFx.doneData, (ids, { answer }) => + ids.filter((s) => s !== answer.card.id), ); function updateCache( diff --git a/src/entities/card/organisms/card-list.tsx b/src/entities/card/organisms/card-list.tsx index a61a2cee..c9f9e732 100644 --- a/src/entities/card/organisms/card-list.tsx +++ b/src/entities/card/organisms/card-list.tsx @@ -26,14 +26,7 @@ export const CardList = ({ cards, loading }: Props) => { return ( {cards.map((card, i) => ( - + ))} ); diff --git a/src/entities/card/organisms/card-preview.tsx b/src/entities/card/organisms/card-preview.tsx index 9534c63b..2cd01a4d 100644 --- a/src/entities/card/organisms/card-preview.tsx +++ b/src/entities/card/organisms/card-preview.tsx @@ -2,7 +2,7 @@ import 'dayjs/plugin/relativeTime'; import dayjs from 'dayjs'; import styled from 'styled-components'; -import React, { forwardRef } from 'react'; +import React, { forwardRef, useCallback } from 'react'; import { Button, IconDeckArrow, @@ -11,23 +11,23 @@ import { Skeleton, Text, } from '@box/shared/ui'; -import type { Card, User } from '@box/shared/api'; +import type { Card } from '@box/shared/api'; import { Editor, useExtendedEditor } from '@cardbox/editor'; import type { EditorValue } from '@cardbox/editor'; import { HighlightText } from '@box/entities/search'; import { Link } from 'react-router-dom'; import { breakpoints } from '@box/shared/lib/breakpoints'; +import { cardModel } from '@box/entities/card'; import { navigationModel } from '@box/entities/navigation'; +import { paths } from '@box/pages/paths'; import { theme } from '@box/shared/lib/theme'; -import { useEvent } from 'effector-react'; +import { useEvent, useStoreMap } from 'effector-react/scope'; import { useMouseSelection } from '@box/shared/lib/use-mouse-selection'; type CardSize = 'small' | 'large'; interface CardPreviewProps { card: Card; - isCardInFavorite?: boolean; - href?: string; loading?: boolean; /** * @remark May be in future - make sense to split independent components - CardItem, CardDetails @@ -38,13 +38,25 @@ interface CardPreviewProps { export const CardPreview = ({ card, - isCardInFavorite = false, - href, loading = false, size = 'small', }: CardPreviewProps) => { + const href = paths.cardView(card.id); + const isCardInFavorites = useStoreMap({ + store: cardModel.$favoritesCards, + keys: [card.id], + fn: (list, [id]) => list.some((card) => card.id === id), + }); + + const addToFavorites = useEvent(cardModel.favoritesAdd); + const removeFromFavorites = useEvent(cardModel.favoritesRemove); const historyPush = useEvent(navigationModel.historyPush); + const toggleFavorites = useCallback(() => { + if (isCardInFavorites) removeFromFavorites(card.id); + else addToFavorites(card.id); + }, [addToFavorites, removeFromFavorites, card.id, isCardInFavorites]); + const { handleMouseDown, handleMouseUp, buttonRef } = useMouseSelection( (inNewTab = false) => { if (!href) return; @@ -66,13 +78,18 @@ export const CardPreview = ({ aria-label="Open card" > - + {size === 'small' && } - + ); }; @@ -120,9 +137,10 @@ const PaperContainerStyled = styled(PaperContainer)<{ } `; -type ContentProps = { card: Card } & Pick; +type ContentProps = { card: Card } & Pick; -const Content = ({ card, size, href }: ContentProps) => { +const Content = ({ card, size }: ContentProps) => { + const href = paths.cardView(card.id); const editor = useExtendedEditor(); return ( @@ -200,35 +218,41 @@ const ContentStyled = styled.div` overflow: hidden; `; -const AddButton = forwardRef( - ({ isCardToDeckAdded }, ref) => { - const handleClick: React.MouseEventHandler = (e) => { - e.stopPropagation(); - }; - - if (isCardToDeckAdded) { - return ( - } - /> - ); - } +const SaveCardButton = forwardRef< + HTMLButtonElement, + { + isCardInFavorites: boolean; + card: Card; + onClick: (id: string) => void; + } +>(({ isCardInFavorites, card, onClick }, ref) => { + const handleClick: React.MouseEventHandler = (e) => { + e.stopPropagation(); + onClick(card.id); + }; + if (isCardInFavorites) { return ( } + theme="primary" + icon={} /> ); - }, -); + } + + return ( + } + /> + ); +}); const ContentBlock = styled.div` display: flex; diff --git a/src/features/card/draft/model.ts b/src/features/card/draft/model.ts index 8c899d29..2c27c86b 100644 --- a/src/features/card/draft/model.ts +++ b/src/features/card/draft/model.ts @@ -1,5 +1,5 @@ import * as editorLib from '@box/shared/lib/editor'; -import type { CardContent } from '@box/shared/api'; +import type { Card, CardContent } from '@box/shared/api'; import { StoreValue, combine, @@ -24,6 +24,7 @@ export const lastTagRemoved = createEvent(); // FIXME: remove after converting to page-unique fabric export const _formInit = createEvent(); +export const setInitialState = createEvent(); export const formReset = createEvent(); const draft = createDomain(); @@ -58,7 +59,7 @@ export type Draft = StoreValue; // Init spread({ - source: internalApi.cardsGet.doneData.map(({ answer }) => answer.card), + source: setInitialState, targets: { id: $id, title: $title, diff --git a/src/pages/card/edit/model.ts b/src/pages/card/edit/model.ts index 9763a45d..06095f7e 100644 --- a/src/pages/card/edit/model.ts +++ b/src/pages/card/edit/model.ts @@ -1,27 +1,29 @@ import * as sessionModel from '@box/entities/session'; +import { Card, internalApi } from '@box/shared/api'; import { attach, createDomain, createEvent, + createStore, guard, merge, sample, } from 'effector'; import { cardDraftModel } from '@box/features/card/draft'; -import { cardModel } from '@box/entities/card'; import { createHatch } from 'framework'; import { historyPush } from '@box/entities/navigation'; -import { internalApi } from '@box/shared/api'; - -import { paths } from '../../paths'; +import { paths } from '@box/pages/paths'; export const hatch = createHatch(createDomain('CardEditPage')); +const $currentCardId = hatch.$params.map((params) => params.cardId || null); export const cardsGetFx = attach({ effect: internalApi.cardsGet }); export const cardUpdateFx = attach({ effect: internalApi.cardsEdit }); +const $currentCard = createStore(null); + // FIXME: may be should be replace to "$errors" in future -export const $isCardFound = cardModel.$currentCard.map((card) => Boolean(card)); +export const $isCardFound = $currentCard.map((card) => Boolean(card)); // Подгружаем данные после монтирования страницы const shouldLoadCard = sample({ @@ -35,6 +37,8 @@ sample({ target: cardsGetFx, }); +$currentCard.on(cardsGetFx.doneData, (_, { answer }) => answer.card); + const cardCtxLoaded = sample({ clock: cardsGetFx.doneData, source: sessionModel.$session, @@ -55,6 +59,20 @@ sample({ target: historyPush, }); +const isAuthorViewing = guard({ + source: cardCtxLoaded, + filter: ({ viewer, card }) => { + if (!viewer) return false; + return viewer.id === card.answer.card.authorId; + }, +}); + +sample({ + clock: isAuthorViewing, + fn: ({ card }) => card.answer.card, + target: cardDraftModel.setInitialState, +}); + // Ивент, который сабмитит форму при отправке ее со страницы редактирования карточки const formEditSubmitted = createEvent(); @@ -91,7 +109,7 @@ guard({ // Возвращаем на страницу карточки после сохранения/отмены изменений sample({ clock: merge([cardUpdateFx.done, formEditReset]), - source: cardModel.$currentCardId, + source: $currentCardId, fn: (cardId) => (cardId ? paths.cardView(cardId) : paths.home()), target: historyPush, }); diff --git a/src/pages/card/view/model.ts b/src/pages/card/view/model.ts index 8343efd5..3f7c2d53 100644 --- a/src/pages/card/view/model.ts +++ b/src/pages/card/view/model.ts @@ -1,4 +1,5 @@ import * as sessionModel from '@box/entities/session'; +import { $session } from '@box/entities/session'; import type { Card, User } from '@box/shared/api'; import { attach, @@ -20,6 +21,7 @@ export const hatch = createHatch(createDomain('CardViewPage')); export const cardsGetFx = attach({ effect: internalApi.cardsGet }); export const cardsDeleteFx = attach({ effect: internalApi.cardsDelete }); export const usersGetFx = attach({ effect: internalApi.usersGet }); +export const cardsListFx = attach({ effect: internalApi.cardsList }); export const deleteCard = createEvent(); @@ -78,3 +80,19 @@ $cardAuthor.reset(hatch.exit); // FIXME: move to entities/user? $cardAuthor.on(cardsGetFx.doneData, (_, { answer }) => answer.user); + +// @TODO Will be deleted after BOX-250 +const favoritesCtxLoaded = sample({ + source: $session, + clock: hatch.enter, + fn: (user) => ({ + body: { authorId: user?.id, favorites: true }, + }), + target: cardsListFx, +}); + +sample({ + source: favoritesCtxLoaded.doneData, + fn: ({ answer }) => answer.cards.map(({ id }) => id), + target: cardModel.changeFavorites, +}); diff --git a/src/pages/card/view/page.tsx b/src/pages/card/view/page.tsx index 97cdba24..4e3814e9 100644 --- a/src/pages/card/view/page.tsx +++ b/src/pages/card/view/page.tsx @@ -1,5 +1,5 @@ -import React from 'react'; import styled from 'styled-components'; +import React, { useCallback } from 'react'; import { Button, ContentCenteredTemplate, @@ -54,12 +54,7 @@ export const CardViewPage = () => {
{card && author && ( - + )} {/* TODO: Process "empty" case correctly */}
diff --git a/src/pages/home/model.ts b/src/pages/home/model.ts index 7f8e1646..c040f8f9 100644 --- a/src/pages/home/model.ts +++ b/src/pages/home/model.ts @@ -1,17 +1,13 @@ +import { $session } from '@box/entities/session'; import type { Card } from '@box/shared/api'; -import { - attach, - combine, - createDomain, - createStore, - restore, - sample, -} from 'effector'; +import { attach, combine, createDomain, createStore, sample } from 'effector'; +import { cardModel } from '@box/entities/card'; import { createHatch } from 'framework'; import { internalApi } from '@box/shared/api'; import { userModel } from '@box/entities/user'; export const cardsFeedFx = attach({ effect: internalApi.cardsFeed }); +export const cardsListFx = attach({ effect: internalApi.cardsList }); export const hatch = createHatch(createDomain('HomePage')); // FIXME: move to entities/card level later? (as cache store?) @@ -45,3 +41,19 @@ sample({ fn: ({ answer }) => [...answer.latest.users, ...answer.top.users], target: userModel.updateMap, }); + +// @TODO Will be deleted after BOX-250 +const favoritesCtxLoaded = sample({ + source: $session, + clock: hatch.enter, + fn: (user) => ({ + body: { authorId: user?.id, favorites: true }, + }), + target: cardsListFx, +}); + +sample({ + source: favoritesCtxLoaded.doneData, + fn: ({ answer }) => answer.cards.map(({ id }) => id), + target: cardModel.changeFavorites, +}); diff --git a/src/pages/home/page.tsx b/src/pages/home/page.tsx index d92d3903..b812c55f 100644 --- a/src/pages/home/page.tsx +++ b/src/pages/home/page.tsx @@ -12,7 +12,7 @@ import { Helmet } from 'react-helmet-async'; import { breakpoints } from '@box/shared/lib/breakpoints'; import { createStore } from 'effector'; import { theme } from '@box/shared/lib/theme'; -import { useStore } from 'effector-react/ssr'; +import { useEvent, useStore } from 'effector-react/ssr'; export const $pagePending = createStore(false); export const $topCards = createStore([]); diff --git a/src/pages/search/model.ts b/src/pages/search/model.ts index e3d93ab1..ac892d49 100644 --- a/src/pages/search/model.ts +++ b/src/pages/search/model.ts @@ -1,7 +1,14 @@ -import { combine, createEvent } from 'effector'; +import { $session } from '@box/entities/session'; +import { attach, combine, createDomain, createEvent, sample } from 'effector'; +import { cardModel } from '@box/entities/card'; +import { createHatch } from 'framework'; +import { internalApi } from '@box/shared/api'; import { searchModel } from '@box/features/search-bar'; +export const cardsListFx = attach({ effect: internalApi.cardsList }); + export const searchQueryChanged = createEvent(); +export const hatch = createHatch(createDomain('SearchPage')); export const $isShowLoading = combine( searchModel.searchFx.pending, @@ -10,3 +17,19 @@ export const $isShowLoading = combine( (isPending, cardsCount, usersCount) => isPending && !cardsCount && !usersCount, ); + +// @TODO Will be deleted after BOX-250 +const favoritesCtxLoaded = sample({ + source: $session, + clock: hatch.enter, + fn: (user) => ({ + body: { authorId: user?.id, favorites: true }, + }), + target: cardsListFx, +}); + +sample({ + source: favoritesCtxLoaded.doneData, + fn: ({ answer }) => answer.cards.map(({ id }) => id), + target: cardModel.changeFavorites, +}); diff --git a/src/pages/user/model.ts b/src/pages/user/model.ts index f5d3d37f..5a35dec3 100644 --- a/src/pages/user/model.ts +++ b/src/pages/user/model.ts @@ -1,7 +1,7 @@ -import { $cardsCache } from '@box/entities/card/model'; import { $session } from '@box/entities/session'; import { User, internalApi } from '@box/shared/api'; import { attach, combine, createDomain, createStore, sample } from 'effector'; +import { cardModel } from '@box/entities/card'; import { createHatch } from 'framework'; import { some } from 'patronum'; @@ -14,14 +14,10 @@ export const $userPending = usersGetFx.pending; export const $cardsPending = cardsListFx.pending; export const $currentUser = createStore(null); const $cardsIds = createStore([]); -const $favoritesIds = createStore([]); -export const $cards = combine($cardsIds, $cardsCache, (ids, { cache }) => - ids.map((id) => cache[id]), -); -export const $favoritesCards = combine( - $favoritesIds, - $cardsCache, - (ids, { cache }) => ids.map((id) => cache[id]), +export const $cards = combine( + $cardsIds, + cardModel.$cardsCache, + (ids, { cache }) => ids.map((id) => cache[id] ?? null), ); export const $isOnOwnedPage = combine( $session, @@ -65,6 +61,18 @@ $cardsIds.on(cardsListFx.done, (ids, { params, result }) => params.body?.favorites ? ids : result.answer.cards.map(({ id }) => id), ); -$favoritesIds.on(cardsListFx.done, (ids, { params, result }) => - params.body?.favorites ? result.answer.cards.map(({ id }) => id) : ids, -); +// @TODO Will be deleted after BOX-250 +const favoritesCtxLoaded = sample({ + source: $session, + clock: hatch.enter, + fn: (user) => ({ + body: { authorId: user?.id, favorites: true }, + }), + target: cardsListFx, +}); + +sample({ + source: favoritesCtxLoaded.doneData, + fn: ({ answer }) => answer.cards.map(({ id }) => id), + target: cardModel.changeFavorites, +}); diff --git a/src/pages/user/page.tsx b/src/pages/user/page.tsx index 32b9aa15..de259bd5 100644 --- a/src/pages/user/page.tsx +++ b/src/pages/user/page.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import styled, { css } from 'styled-components'; import { Avatar, @@ -11,7 +11,7 @@ import { iconUserBg, } from '@box/shared/ui'; import { Card, User } from '@box/shared/api'; -import { CardList } from '@box/entities/card'; +import { CardList, cardModel } from '@box/entities/card'; import { ShowOnly } from '@box/entities/session'; import { breakpoints } from '@box/shared/lib/breakpoints'; import { createStore } from 'effector'; @@ -25,7 +25,6 @@ import { SkeletonLayout } from './skeleton'; export const $currentUser = createStore(null); export const $cards = createStore([]); -export const $favoritesCards = createStore([]); export const $pagePending = createStore(false); export const $isOnOwnedPage = createStore(false); @@ -50,7 +49,7 @@ const UserPageContentComponent = () => { const userInfo = useStore($currentUser)!; const isOnOwnedPage = useStore($isOnOwnedPage); const cards = useStore($cards); - const favoritesCards = useStore($favoritesCards); + const favoritesCards = useStore(cardModel.$favoritesCards); const { work, bio, socials, avatar } = userInfo; const fullName = userLib.getFullName(userInfo); diff --git a/src/shared/api/internal/index.gen.ts b/src/shared/api/internal/index.gen.ts index 74a1bea2..85257a4f 100644 --- a/src/shared/api/internal/index.gen.ts +++ b/src/shared/api/internal/index.gen.ts @@ -637,6 +637,7 @@ export const cardsGet = createEffect({ export interface CardsCreate { body?: { title: string; + // eslint-disable-next-line @typescript-eslint/ban-types content: {}; tags?: string[]; }; @@ -711,6 +712,7 @@ export interface CardsEdit { body?: { cardId: string; title?: string; + // eslint-disable-next-line @typescript-eslint/ban-types content?: {}; tags?: string[]; }; @@ -902,6 +904,79 @@ export const cardsSave = createEffect({ }); //#endregion cardsSave +/* --- */ +//#region cardsUnsave +export interface CardsUnsave { + body?: { + cardId: string; + }; +} + +/* OK */ +export const cardsUnsaveOk = typed.object({ + card: typed.object({ + id: typed.string, + title: typed.string, + content: typed.array(typed.object({})), + createdAt: typed.string, + updatedAt: typed.string, + + /* Author user uuid */ + authorId: typed.string, + + /* Later, we can create `Tag` entity */ + tags: typed.array(typed.string), + + /* Later, we can add this field + * For custom text-overflow (instead of truncating with emphasizing) */ + summary: typed.string.maybe, + }), + boxId: typed.string, +}); +export interface CardsUnsaveDone { + status: 'ok'; + answer: typed.Get; +} + +/* CLIENT_ERROR */ +export const cardsUnsaveBadRequest = typed.object({ + error: typed.boolean, + code: typed.union('already_unsaved', 'card_not_found', 'no_access'), +}); + +/* SERVER_ERROR */ +export const cardsUnsaveInternalServerError = typed.nul; +export type CardsUnsaveFail = + | { + status: 'bad_request'; + error: typed.Get; + } + | { + status: 'internal_server_error'; + error: typed.Get; + } + | GenericErrors; +export const cardsUnsave = createEffect< + CardsUnsave, + CardsUnsaveDone, + CardsUnsaveFail +>({ + async handler({ body }) { + const name = 'cardsUnsave.body'; + const response = await requestFx({ + path: '/cards.unsave', + method: 'POST', + body, + }); + return parseByStatus(name, response, { + 200: ['ok', cardsUnsaveOk], + 400: ['bad_request', cardsUnsaveBadRequest], + 500: ['internal_server_error', cardsUnsaveInternalServerError], + }); + }, +}); +//#endregion cardsUnsave + /* --- */ //#region sessionGet export interface SessionGet {} diff --git a/src/shared/api/types.ts b/src/shared/api/types.ts index 5047c814..4fb86ca3 100644 --- a/src/shared/api/types.ts +++ b/src/shared/api/types.ts @@ -1,11 +1,13 @@ import type { EditorValue } from '@cardbox/editor'; +import { CardsGetDone, SessionGetDone, UsersGetDone } from './internal'; + // Экспортируем отдельно, чтобы могли обращаться к типу, не зная, про реализацию (Editor) export type CardContent = EditorValue; -export type Card = import('./internal').CardsGetDone['answer']['card']; +export type Card = CardsGetDone['answer']['card']; -export type User = import('./internal').UsersGetDone['answer']['user']; +export type User = UsersGetDone['answer']['user']; // FIXME: Просчитывать позднее от User - пока что не получается из-за non-required поля export interface UserSocial { @@ -15,7 +17,7 @@ export interface UserSocial { readonly username: string; } -export type SessionUser = import('./internal').SessionGetDone['answer']['user']; +export type SessionUser = SessionGetDone['answer']['user']; // FIXME: implement on real API