Get highlights with library items, over fetching for now, can maybe trim stuff in future

This commit is contained in:
Jackson Harper 2023-03-06 22:13:50 +08:00
parent 079b6e5c41
commit e13b809389
6 changed files with 221 additions and 397 deletions

View file

@ -12,21 +12,21 @@ type HighlightViewProps = {
scrollToHighlight?: (arg: string) => void
}
const StyledQuote = styled(Blockquote, {
margin: '0px 0px 0px 0px',
fontSize: '18px',
lineHeight: '27px',
color: '$grayText',
padding: '0px 16px',
borderLeft: '2px solid $omnivoreCtaYellow',
})
export function HighlightView(props: HighlightViewProps): JSX.Element {
const lines = useMemo(
() => props.highlight.quote.split('\n'),
[props.highlight.quote]
)
const StyledQuote = styled(Blockquote, {
margin: '0px 0px 0px 0px',
fontSize: '18px',
lineHeight: '27px',
color: '$grayText',
padding: '0px 16px',
borderLeft: '2px solid $omnivoreCtaYellow',
})
return (
<VStack css={{ width: '100%', boxSizing: 'border-box' }}>
<StyledQuote
@ -58,102 +58,3 @@ export function HighlightView(props: HighlightViewProps): JSX.Element {
</VStack>
)
}
export function PublicHighlightView(props: HighlightViewProps): JSX.Element {
const lines = useMemo(
() => props.highlight.quote.split('\n'),
[props.highlight.quote]
)
const StyledQuote = styled(Blockquote, {
margin: '16px 0px 0px 0px',
fontSize: '18px',
lineHeight: '1.5',
fontFamily: 'Inter',
color: '$readerFont',
})
return (
<VStack css={{ width: '100%', bg: '$grayBackground', pt: '0px' }}>
<StyledQuote>
{props.highlight.prefix}
<SpanBox
css={{
bg: '$omnivoreYellow',
p: '1px',
borderRadius: '2px',
color: '#3d3d3d',
}}
>
{lines.map((line: string, index: number) => (
<Fragment key={index}>
{line}
{index !== lines.length - 1 && (
<>
<br />
<br />
</>
)}
</Fragment>
))}
</SpanBox>
{props.highlight.suffix}
</StyledQuote>
</VStack>
)
}
type HighlightFooterProps = {
site: string
author?: string
title?: string
fontSize?: string
}
export function HighlightFooter(props: HighlightFooterProps): JSX.Element {
const fontSize = props.fontSize || '12px'
return (
<VStack
alignment="start"
css={{ width: '100%', bg: '$grayBackground', pt: '0px', pb: '8px' }}
>
<StyledText
css={{
fontSize: fontSize,
fontFamily: 'Inter',
color: '$omnivoreGray',
fontWeight: '400',
mb: '0px',
p: '0px',
}}
>
From{' '}
<SpanBox
css={{
fontSize: fontSize,
color: '$omnivoreOrange',
p: '0px',
fontWeight: '400',
mb: '0px',
}}
>
{props.title}
{props.author ? `${props.author}` : null}
</SpanBox>
</StyledText>
<StyledText
css={{
pt: '0px',
mt: '0px',
fontSize: '10px',
fontFamily: 'Inter',
color: '$omnivoreGray',
fontWeight: '400',
}}
>
{props.site}
</StyledText>
</VStack>
)
}

View file

@ -1,7 +1,17 @@
import { styled } from '@stitches/react'
import { VStack, HStack } from '../../elements/LayoutPrimitives'
import { Fragment, useMemo } from 'react'
import { LabelChip } from '../../elements/LabelChip'
import {
VStack,
HStack,
Blockquote,
SpanBox,
Box,
} from '../../elements/LayoutPrimitives'
import { StyledMark, StyledText } from '../../elements/StyledText'
import { HighlightView } from '../HighlightView'
import { LinkedItemCardAction, LinkedItemCardProps } from './CardTypes'
import { TitleStyle } from './LibraryCardStyles'
export interface HighlightItemCardProps
extends Pick<LinkedItemCardProps, 'item'> {
@ -13,18 +23,30 @@ export const PreviewImage = styled('img', {
cursor: 'pointer',
})
const StyledQuote = styled(Blockquote, {
margin: '0px 0px 0px 0px',
fontSize: '16px',
lineHeight: '27px',
color: '$grayText',
paddingLeft: '20px',
// borderRadius: '8px',
borderLeft: '2px solid $omnivoreCtaYellow',
})
export function HighlightItemCard(props: HighlightItemCardProps): JSX.Element {
const lines = useMemo(() => props.item.quote.split('\n'), [props.item.quote])
return (
<VStack
css={{
p: '$2',
p: '20px',
gap: '20px',
height: '100%',
width: '100%',
borderRadius: '0px',
width: '320px',
borderRadius: '8px',
cursor: 'pointer',
wordBreak: 'break-word',
overflow: 'clip',
border: '1px solid $grayBorder',
border: '1px solid $thBorderColor',
borderBottom: 'none',
boxShadow: '0px 3px 11px rgba(32, 31, 29, 0.04)',
bg: '$grayBg',
@ -41,48 +63,34 @@ export function HighlightItemCard(props: HighlightItemCardProps): JSX.Element {
props.handleAction('showDetail')
}}
>
<StyledText
<Box
css={{
lineHeight: '20px',
...TitleStyle,
height: '42px',
}}
>
<StyledMark
css={{
background: 'rgb($highlightBackground)',
color: '$highlightText',
fontSize: '14px',
}}
>
{props.item.quote}
</StyledMark>
</StyledText>
<HStack
css={{
marginTop: 'auto',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
{props.item.image && (
<PreviewImage
src={props.item.image}
alt="Preview Image"
width={16}
height={16}
css={{ borderRadius: '50%' }}
onError={(e) => {
;(e.target as HTMLElement).style.display = 'none'
}}
/>
)}
<StyledText
css={{
fontWeight: '700',
}}
>
{props.item.title}
</StyledText>
</HStack>
{props.item.title}
</Box>
<StyledQuote>
<SpanBox css={{ p: '1px', borderRadius: '2px' }}>
{lines.map((line: string, index: number) => (
<Fragment key={index}>
{line}
{index !== lines.length - 1 && (
<>
<br />
<br />
</>
)}
</Fragment>
))}
</SpanBox>
<Box css={{ display: 'block', pt: '16px' }}>
{props.item.labels?.map(({ name, color }, index) => (
<LabelChip key={index} text={name || ''} color={color} />
))}
</Box>
</StyledQuote>
</VStack>
)
}

View file

@ -138,6 +138,9 @@ export function LibraryGridCard(props: LinkedItemCardProps): JSX.Element {
Math.round((props.item.wordsCount ?? 0) / 235)
)} min read`
: null}
{props.item.highlights?.length ?? 0 > 0
? `${props.item.highlights?.length} highlights`
: null}
</Box>
<Box
css={{

View file

@ -116,6 +116,9 @@ export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
</SpanBox>
</>
) : null}
{props.item.highlights?.length ?? 0 > 0
? `${props.item.highlights?.length} highlights`
: null}
</Box>
<Box
css={{

View file

@ -604,6 +604,33 @@ type HomeFeedContentProps = {
) => Promise<void>
}
const DragnDropContainer = styled('div', {
width: '100%',
height: '80%',
position: 'absolute',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: '1',
alignSelf: 'center',
left: 0,
})
const DragnDropStyle = styled('div', {
border: '3px dashed gray',
backgroundColor: 'aliceblue',
borderRadius: '5px',
width: '100%',
height: '100%',
opacity: '0.9',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
alignSelf: 'center',
left: 0,
margin: '16px',
})
function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
const { viewerData } = useGetViewerQuery()
const [layout, setLayout] = usePersistedState<LayoutType>({
@ -625,33 +652,6 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
const [, updateState] = useState({})
const DragnDropContainer = styled('div', {
width: '100%',
height: '80%',
position: 'absolute',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: '1',
alignSelf: 'center',
left: 0,
})
const DragnDropStyle = styled('div', {
border: '3px dashed gray',
backgroundColor: 'aliceblue',
borderRadius: '5px',
width: '100%',
height: '100%',
opacity: '0.9',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
alignSelf: 'center',
left: 0,
margin: '16px',
})
const removeItem = () => {
if (!props.linkToRemove) {
return
@ -840,100 +840,24 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
}}
/>
) : (
<Box
ref={props.gridContainerRef}
css={{
py: '$3',
display: 'grid',
width: '100%',
gridAutoRows: 'auto',
borderRadius: '8px',
gridGap: layout == 'LIST_LAYOUT' ? '0' : '20px',
marginTop: layout == 'LIST_LAYOUT' ? '21px' : '0',
marginBottom: '0px',
paddingTop: layout == 'LIST_LAYOUT' ? '0' : '21px',
paddingBottom: layout == 'LIST_LAYOUT' ? '0px' : '21px',
overflow: 'hidden',
'@xlgDown': {
border: 'unset',
borderRadius: layout == 'LIST_LAYOUT' ? 0 : undefined,
},
'@smDown': {
border: 'unset',
width: layout == 'LIST_LAYOUT' ? '100vw' : undefined,
margin:
layout == 'LIST_LAYOUT' ? '16px -16px' : undefined,
borderRadius: layout == 'LIST_LAYOUT' ? 0 : undefined,
},
'@md': {
gridTemplateColumns:
layout == 'LIST_LAYOUT' ? 'none' : '1fr 1fr',
},
'@lg': {
gridTemplateColumns:
layout == 'LIST_LAYOUT' ? 'none' : 'repeat(2, 1fr)',
},
'@xl': {
gridTemplateColumns:
layout == 'LIST_LAYOUT' ? 'none' : 'repeat(3, 1fr)',
},
'@xxl': {
gridTemplateColumns:
layout == 'LIST_LAYOUT' ? 'none' : 'repeat(4, 1fr)',
},
}}
>
{props.items.map((linkedItem) => (
<Box
className="linkedItemCard"
data-testid="linkedItemCard"
id={linkedItem.node.id}
tabIndex={0}
key={linkedItem.node.id}
css={{
width: '100%',
'&> div': {
bg: '$thBackground3',
},
'&:focus': {
'> div': {
bg: '$thBackgroundActive',
},
},
'&:hover': {
'> div': {
bg: '$thBackgroundActive',
},
'> a': {
bg: '$thBackgroundActive',
},
},
}}
>
{viewerData?.me && (
<LinkedItemCard
layout={layout}
item={linkedItem.node}
viewer={viewerData.me}
handleAction={(action: LinkedItemCardAction) => {
if (action === 'delete') {
setShowRemoveLinkConfirmation(true)
props.setLinkToRemove(linkedItem)
} else if (action === 'editTitle') {
props.setShowEditTitleModal(true)
props.setLinkToEdit(linkedItem)
} else if (action == 'unsubscribe') {
setShowUnsubscribeConfirmation(true)
props.setLinkToUnsubscribe(linkedItem)
} else {
props.actionHandler(action, linkedItem)
}
}}
/>
)}
</Box>
))}
</Box>
<LibraryItems
items={props.items}
layout={layout}
viewer={viewerData?.me}
gridContainerRef={props.gridContainerRef}
showEditTitleModal={props.showEditTitleModal}
setShowEditTitleModal={props.setShowEditTitleModal}
setLinkToEdit={props.setLinkToEdit}
setShowUnsubscribeConfirmation={
setShowUnsubscribeConfirmation
}
setLinkToRemove={props.setLinkToRemove}
setLinkToUnsubscribe={props.setLinkToUnsubscribe}
setShowRemoveLinkConfirmation={
setShowRemoveLinkConfirmation
}
actionHandler={props.actionHandler}
/>
)}
<HStack
distribution="center"
@ -1042,141 +966,120 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
)
}
type LegacyMobileTopNavProps = {
viewer?: UserBasicData
searchTerm?: string
applySearchQuery: (searchQuery: string) => void
type LibraryItemsProps = {
items: LibraryItem[]
layout: LayoutType
updateLayout: (layout: LayoutType) => void
viewer: UserBasicData | undefined
setShowAddLinkModal: (show: boolean) => void
gridContainerRef: React.RefObject<HTMLDivElement>
setShowEditTitleModal: (show: boolean) => void
setLinkToEdit: (set: LibraryItem | undefined) => void
setShowUnsubscribeConfirmation: (show: true) => void
setLinkToRemove: (set: LibraryItem | undefined) => void
setLinkToUnsubscribe: (set: LibraryItem | undefined) => void
setShowRemoveLinkConfirmation: (show: true) => void
actionHandler: (
action: LinkedItemCardAction,
item: LibraryItem | undefined
) => Promise<void>
}
function LegacyMobileTopNav(props: LegacyMobileTopNavProps): JSX.Element {
const StyledToggleButton = styled('button', {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
p: '0px',
backgroundColor: 'transparent',
border: 'none',
cursor: 'pointer',
width: '32px',
height: '32px',
borderRadius: '4px',
'&:hover': {
opacity: 0.8,
},
'&[data-state="on"]': {
bg: 'rgb(43, 43, 43)',
},
})
function LibraryItems(props: LibraryItemsProps): JSX.Element {
return (
<>
<HStack
alignment="center"
distribution="start"
css={{ width: '100%', '@md': { display: 'none' } }}
>
<StyledText
style="subHeadline"
css={{
mr: '32px',
'@smDown': {
mr: '16px',
},
}}
>
Library
</StyledText>
<Box
ref={props.gridContainerRef}
css={{
py: '$3',
display: 'grid',
width: '100%',
gridAutoRows: 'auto',
borderRadius: '8px',
gridGap: props.layout == 'LIST_LAYOUT' ? '0' : '20px',
marginTop: props.layout == 'LIST_LAYOUT' ? '21px' : '0',
marginBottom: '0px',
paddingTop: props.layout == 'LIST_LAYOUT' ? '0' : '21px',
paddingBottom: props.layout == 'LIST_LAYOUT' ? '0px' : '21px',
overflow: 'hidden',
'@xlgDown': {
border: 'unset',
borderRadius: props.layout == 'LIST_LAYOUT' ? 0 : undefined,
},
'@smDown': {
border: 'unset',
width: props.layout == 'LIST_LAYOUT' ? '100vw' : undefined,
margin: props.layout == 'LIST_LAYOUT' ? '16px -16px' : undefined,
borderRadius: props.layout == 'LIST_LAYOUT' ? 0 : undefined,
},
'@md': {
gridTemplateColumns:
props.layout == 'LIST_LAYOUT' ? 'none' : '1fr 1fr',
},
'@lg': {
gridTemplateColumns:
props.layout == 'LIST_LAYOUT' ? 'none' : 'repeat(2, 1fr)',
},
'@xl': {
gridTemplateColumns:
props.layout == 'LIST_LAYOUT' ? 'none' : 'repeat(3, 1fr)',
},
'@xxl': {
gridTemplateColumns:
props.layout == 'LIST_LAYOUT' ? 'none' : 'repeat(4, 1fr)',
},
}}
>
{props.items.map((linkedItem) => (
<Box
className="linkedItemCard"
data-testid="linkedItemCard"
id={linkedItem.node.id}
tabIndex={0}
key={linkedItem.node.id}
css={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<StyledToggleButton
data-state={props.layout === 'GRID_LAYOUT' ? 'on' : 'off'}
onClick={() => {
props.updateLayout('GRID_LAYOUT')
}}
>
<GridLayoutIcon color={'rgb(211, 211, 213)'} />
</StyledToggleButton>
<StyledToggleButton
data-state={props.layout === 'LIST_LAYOUT' ? 'on' : 'off'}
onClick={() => {
props.updateLayout('LIST_LAYOUT')
}}
>
<ListLayoutIcon color={'rgb(211, 211, 213)'} />
</StyledToggleButton>
</Box>
<Button
style="ctaDarkYellow"
css={{ marginLeft: 'auto' }}
onClick={() => {
props.setShowAddLinkModal(true)
}}
>
Add Link
</Button>
</HStack>
<LibrarySearchBar
searchTerm={props.searchTerm}
applySearchQuery={props.applySearchQuery}
/>
{props.viewer && (
<Box
css={{
display: 'flex',
width: '100%',
height: '44px',
marginTop: '16px',
gap: '8px',
flexDirection: 'row',
overflowY: 'scroll',
scrollbarWidth: 'none',
'&::-webkit-scrollbar': {
display: 'none',
'&> div': {
bg: '$thBackground3',
},
'&:focus': {
'> div': {
bg: '$thBackgroundActive',
},
},
'&:hover': {
'> div': {
bg: '$thBackgroundActive',
},
'> a': {
bg: '$thBackgroundActive',
},
},
}}
>
{Object.keys(SAVED_SEARCHES).map((key) => {
const isInboxTerm = (term: string) => {
return !term || term === 'in:inbox'
}
const searchQuery = SAVED_SEARCHES[key]
const style =
searchQuery === props.searchTerm ||
(!props.searchTerm && isInboxTerm(searchQuery))
? 'ctaDarkYellow'
: 'ctaLightGray'
return (
<Button
key={key}
style={style}
onClick={() => {
props.applySearchQuery(searchQuery)
}}
css={{
p: '10px 12px',
height: '37.5px',
borderRadius: '6px',
whiteSpace: 'nowrap',
}}
>
{key}
</Button>
)
})}
{props.viewer && (
<LinkedItemCard
layout={props.layout}
item={linkedItem.node}
viewer={props.viewer}
handleAction={(action: LinkedItemCardAction) => {
if (action === 'delete') {
props.setShowRemoveLinkConfirmation(true)
props.setLinkToRemove(linkedItem)
} else if (action === 'editTitle') {
props.setShowEditTitleModal(true)
props.setLinkToEdit(linkedItem)
} else if (action == 'unsubscribe') {
props.setShowUnsubscribeConfirmation(true)
props.setLinkToUnsubscribe(linkedItem)
} else {
props.actionHandler(action, linkedItem)
}
}}
/>
)}
</Box>
)}
</>
))}
</Box>
)
}

View file

@ -9,6 +9,7 @@ import { unsubscribeMutation } from '../mutations/unsubscribeMutation'
import { articleReadingProgressMutation } from '../mutations/articleReadingProgressMutation'
import { Label } from './../fragments/labelFragment'
import { showErrorToast, showSuccessToast } from '../../toastHelpers'
import { Highlight, highlightFragment } from '../fragments/highlightFragment'
export type LibraryItemsQueryInput = {
limit: number
@ -85,6 +86,7 @@ export type LibraryItemNode = {
savedAt?: string
wordsCount?: number
recommendations?: Recommendation[]
highlights?: Highlight[]
}
export type Recommendation = {
@ -182,6 +184,9 @@ export function useGetLibraryItemsQuery({
}
recommendedAt
}
highlights {
...HighlightFields
}
}
}
pageInfo {
@ -197,6 +202,7 @@ export function useGetLibraryItemsQuery({
}
}
}
${highlightFragment}
`
const variables = {