add basic highlights web UI

This commit is contained in:
Hongbo Wu 2024-06-08 10:50:44 +08:00
parent 28255e073f
commit 470413b704
4 changed files with 186 additions and 12 deletions

View file

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

View file

@ -210,10 +210,10 @@ const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => {
filterTerm="in:library use:folders"
icon={<LibraryIcon color={theme.colors.ctaBlue.toString()} />}
/>
<NavButton
<NavRedirectButton
{...props}
text="Highlights"
filterTerm="in:all has:highlights mode:highlights"
redirectLocation={'/highlights'}
icon={<HighlightsIcon color={theme.colors.highlight.toString()} />}
/>
<NavRedirectButton

View file

@ -5,11 +5,10 @@ import { gqlFetcher } from '../networkHelpers'
import { PageInfo } from './useGetLibraryItemsQuery'
interface HighlightsResponse {
highlightsData?: Array<HighlightsData>
highlightsError?: unknown
data?: Array<HighlightsData>
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,

View file

@ -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 (
<Box
css={{
width: '100%',
height: `100vh`,
}}
>
<EmptyHighlights />
</Box>
)
}
return (
<HStack>
<LibraryFilterMenu
setShowAddLinkModal={setShowAddLinkModal}
showFilterMenu={showFilterMenu}
setShowFilterMenu={setShowFilterMenu}
searchTerm={undefined}
applySearchQuery={(searchQuery: string) => {
router?.push(`/home?q=${searchQuery}`)
}}
/>
<VStack
css={{
maxWidth: '70%',
padding: '20px',
margin: '30px 50px 0 0',
}}
>
{highlights.map((highlight) => {
return <HighlightCard key={highlight.id} highlight={highlight} />
})}
</VStack>
</HStack>
)
}
interface HighlightCardProps {
highlight: Highlight
}
function HighlightCard(props: HighlightCardProps): JSX.Element {
return (
<Box
css={{
width: '100%',
fontFamily: '$inter',
padding: '20px',
marginBottom: '20px',
bg: '$thBackground2',
borderRadius: '8px',
}}
>
<VStack
css={{
width: '100%',
}}
>
<Box
css={{
width: '30px',
height: '5px',
backgroundColor: highlightColor(props.highlight.color),
borderRadius: '2px',
}}
/>
{props.highlight.quote && (
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{props.highlight.quote}
</ReactMarkdown>
)}
{props.highlight.labels && (
<HStack
css={{
marginBottom: '10px',
}}
>
{props.highlight.labels.map((label) => {
return (
<LabelChip
key={label.id}
color={label.color}
text={label.name}
/>
)
})}
</HStack>
)}
<Box
css={{
color: '$thText',
fontSize: '12px',
lineHeight: '20px',
fontWeight: 300,
marginBottom: '10px',
}}
>
{props.highlight.libraryItem?.title}
</Box>
<Box
css={{
color: '$grayText',
fontSize: '12px',
lineHeight: '20px',
fontWeight: 300,
}}
>
{props.highlight.libraryItem?.author}
</Box>
</VStack>
</Box>
)
}