From 470413b70405cd00ace4146f719487dcb5532ead Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Sat, 8 Jun 2024 10:50:44 +0800 Subject: [PATCH] add basic highlights web UI --- packages/api/src/services/highlights.ts | 5 + .../templates/navMenu/LibraryMenu.tsx | 4 +- .../networking/queries/useGetHighlights.tsx | 23 +-- packages/web/pages/highlights.tsx | 166 ++++++++++++++++++ 4 files changed, 186 insertions(+), 12 deletions(-) create mode 100644 packages/web/pages/highlights.tsx diff --git a/packages/api/src/services/highlights.ts b/packages/api/src/services/highlights.ts index 80a011589..327038aca 100644 --- a/packages/api/src/services/highlights.ts +++ b/packages/api/src/services/highlights.ts @@ -290,6 +290,11 @@ export const searchHighlights = async ( const queryBuilder = tx .getRepository(Highlight) .createQueryBuilder('highlight') + .innerJoin( + 'highlight.libraryItem', + 'libraryItem', + 'highlight.libraryItemId = libraryItem.id AND libraryItem.deletedAt IS NULL' + ) .andWhere('highlight.userId = :userId', { userId }) .orderBy('highlight.updatedAt', 'DESC') .take(limit) diff --git a/packages/web/components/templates/navMenu/LibraryMenu.tsx b/packages/web/components/templates/navMenu/LibraryMenu.tsx index e39923bca..d852a2087 100644 --- a/packages/web/components/templates/navMenu/LibraryMenu.tsx +++ b/packages/web/components/templates/navMenu/LibraryMenu.tsx @@ -210,10 +210,10 @@ const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => { filterTerm="in:library use:folders" icon={} /> - } /> - highlightsError?: unknown + data?: Array + error?: unknown isLoading: boolean isValidating: boolean - error: boolean size: number setSize: ( size: number | ((_size: number) => number) @@ -19,7 +18,6 @@ interface HighlightsResponse { interface HighlightsVariables { first?: number - after?: string query?: string } @@ -46,6 +44,11 @@ export const useGetHighlights = ( edges { node { ...HighlightFields + libraryItem { + id + title + author + } } cursor } @@ -67,12 +70,13 @@ export const useGetHighlights = ( const getKey = (pageIndex: number, previousPageData: any) => { if (previousPageData && !previousPageData.highlights.edges) return null - if (pageIndex === 0) return `${query}_${variables.first}_${variables.query}` + if (pageIndex === 0) return '0' - return `${query}_${previousPageData.highlights.pageInfo.endCursor}_${variables.first}_${variables.query}` + return previousPageData.highlights.pageInfo.endCursor } - const fetcher = async () => gqlFetcher(query, variables, true) + const fetcher = async (cursor: string | null) => + gqlFetcher(query, { ...variables, after: cursor }, true) const { data, error, isValidating, mutate, size, setSize } = useSWRInfinite( getKey, @@ -99,10 +103,9 @@ export const useGetHighlights = ( return { isValidating, - highlightsData: responsePages || undefined, - highlightsError: responseError, + data: responsePages || undefined, + error: responseError, isLoading: !error && !data, - error: !!error, size, setSize, mutate, diff --git a/packages/web/pages/highlights.tsx b/packages/web/pages/highlights.tsx new file mode 100644 index 000000000..3a4cbd551 --- /dev/null +++ b/packages/web/pages/highlights.tsx @@ -0,0 +1,166 @@ +import { useRouter } from 'next/router' +import { useCallback, useMemo, useState } from 'react' +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' +import { LabelChip } from '../components/elements/LabelChip' +import { Box, HStack, VStack } from '../components/elements/LayoutPrimitives' +import { EmptyHighlights } from '../components/templates/homeFeed/EmptyHighlights' +import { LibraryFilterMenu } from '../components/templates/navMenu/LibraryMenu' +import { useApplyLocalTheme } from '../lib/hooks/useApplyLocalTheme' +import { useFetchMore } from '../lib/hooks/useFetchMoreScroll' +import { Highlight } from '../lib/networking/fragments/highlightFragment' +import { useGetHighlights } from '../lib/networking/queries/useGetHighlights' +import { highlightColor } from '../lib/themeUpdater' + +const PAGE_SIZE = 10 + +export default function HighlightsPage(): JSX.Element { + const router = useRouter() + const [showFilterMenu, setShowFilterMenu] = useState(false) + const [_, setShowAddLinkModal] = useState(false) + + useApplyLocalTheme() + + const { isLoading, isValidating, mutate, setSize, size, data, error } = + useGetHighlights({ + first: PAGE_SIZE, + }) + + const hasMore = useMemo(() => { + if (!data) { + return false + } + return data[data.length - 1].highlights.pageInfo.hasNextPage + }, [data]) + + const handleFetchMore = useCallback(() => { + if (isLoading || !hasMore) { + return + } + setSize(size + 1) + }, [isLoading, hasMore, setSize, size]) + + useFetchMore(handleFetchMore) + + const highlights = useMemo(() => { + if (!data) { + return [] + } + return data.flatMap((res) => res.highlights.edges.map((edge) => edge.node)) + }, [data]) + + if (!highlights.length) { + return ( + + + + ) + } + + return ( + + { + router?.push(`/home?q=${searchQuery}`) + }} + /> + + {highlights.map((highlight) => { + return + })} + + + ) +} + +interface HighlightCardProps { + highlight: Highlight +} + +function HighlightCard(props: HighlightCardProps): JSX.Element { + return ( + + + + {props.highlight.quote && ( + + {props.highlight.quote} + + )} + {props.highlight.labels && ( + + {props.highlight.labels.map((label) => { + return ( + + ) + })} + + )} + + {props.highlight.libraryItem?.title} + + + {props.highlight.libraryItem?.author} + + + + ) +}