Merge pull request #396 from omnivore-app/feature/search-highlights-web

Feature/search highlights web
This commit is contained in:
Jackson Harper
2022-05-04 09:11:38 -07:00
committed by GitHub
13 changed files with 390 additions and 153 deletions

View File

@ -177,3 +177,7 @@ export const StyledImg = styled('img', {
export const StyledAnchor = styled('a', {
textDecoration: 'none'
})
export const StyledMark = styled('mark', {
})

View File

@ -8,6 +8,7 @@ type HighlightViewProps = {
highlight: Highlight
author?: string
title?: string
scrollToHighlight?: (arg: string) => void;
}
export function HighlightView(props: HighlightViewProps): JSX.Element {
@ -22,6 +23,7 @@ export function HighlightView(props: HighlightViewProps): JSX.Element {
fontSize: '18px',
lineHeight: '27px',
color: '$textDefault',
cursor: 'pointer',
})
return (
@ -31,7 +33,11 @@ export function HighlightView(props: HighlightViewProps): JSX.Element {
<StyledText style='shareHighlightModalAnnotation'>{annotation}</StyledText>
</Box>)
}
<StyledQuote>
<StyledQuote onClick={() => {
if (props.scrollToHighlight) {
props.scrollToHighlight(props.highlight.id)
}
}}>
{props.highlight.prefix}
<SpanBox css={{ bg: '$highlightBackground', p: '1px', borderRadius: '2px', }}>
{lines.map((line: string, index: number) => (

View File

@ -0,0 +1,89 @@
import { styled } from '@stitches/react'
import { VStack, HStack } from '../../elements/LayoutPrimitives'
import { StyledMark, StyledText } from '../../elements/StyledText'
import { LinkedItemCardAction, LinkedItemCardProps } from './CardTypes'
export interface HighlightItemCardProps
extends Pick<LinkedItemCardProps, 'item'> {
handleAction: (action: LinkedItemCardAction) => void
}
export const PreviewImage = styled('img', {
objectFit: 'cover',
cursor: 'pointer',
})
export function HighlightItemCard(props: HighlightItemCardProps): JSX.Element {
return (
<VStack
css={{
p: '$2',
height: '100%',
maxWidth: '498px',
borderRadius: '6px',
cursor: 'pointer',
wordBreak: 'break-word',
overflow: 'clip',
border: '1px solid $grayBorder',
boxShadow: '0px 3px 11px rgba(32, 31, 29, 0.04)',
bg: '$grayBg',
'&:focus': {
bg: '$grayBgActive',
},
'&:hover': {
bg: '$grayBgActive',
},
}}
alignment="start"
distribution="start"
onClick={() => {
props.handleAction('showDetail')
}}
>
<StyledText
css={{
lineHeight: '20px',
}}
>
<StyledMark
css={{
background: '$highlightBackground',
color: '$highlightText',
}}
>
{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={{
marginLeft: '$2',
fontWeight: '700',
}}
>
{props.item.title
.substring(0, 50)
.concat(props.item.title.length > 50 ? '...' : '')}
</StyledText>
</HStack>
</VStack>
)
}

View File

@ -1,6 +1,8 @@
import { GridLinkedItemCard } from './GridLinkedItemCard'
import { ListLinkedItemCard } from './ListLinkedItemCard'
import type { LinkedItemCardProps } from './CardTypes'
import { HighlightItemCard } from './HighlightItemCard'
import { PageType } from '../../../lib/networking/fragments/articleFragment'
const siteName = (originalArticleUrl: string, itemUrl: string): string => {
try {
@ -15,6 +17,9 @@ const siteName = (originalArticleUrl: string, itemUrl: string): string => {
export function LinkedItemCard(props: LinkedItemCardProps): JSX.Element {
const originText = siteName(props.item.originalArticleUrl, props.item.url)
if (props.item.pageType === PageType.HIGHLIGHTS) {
return <HighlightItemCard {...props} />
}
if (props.layout == 'LIST_LAYOUT') {
return <ListLinkedItemCard {...props} originText={originText} />
} else {

View File

@ -21,8 +21,10 @@ import { ArticleMutations } from '../../../lib/articleActions'
export type ArticleProps = {
articleId: string
content: string
highlightReady: boolean
initialAnchorIndex: number
initialReadingProgress?: number
highlightHref: MutableRefObject<string | null>
scrollElementRef: MutableRefObject<HTMLDivElement | null>
articleMutations: ArticleMutations
}
@ -139,50 +141,53 @@ export function Article(props: ArticleProps): JSX.Element {
return
}
if (!shouldScrollToInitialPosition) {
return
}
setShouldScrollToInitialPosition(false)
if (props.initialReadingProgress && props.initialReadingProgress >= 98) {
return
}
const anchorElement = document.querySelector(
`[data-omnivore-anchor-idx='${props.initialAnchorIndex.toString()}']`
)
if (anchorElement) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const calculateOffset = (obj: any): number => {
let offset = 0
if (obj.offsetParent) {
do {
offset += obj.offsetTop
} while ((obj = obj.offsetParent))
return offset
}
return 0
if (props.highlightReady) {
if (!shouldScrollToInitialPosition) {
return
}
if (props.scrollElementRef.current) {
props.scrollElementRef.current?.scroll(
0,
calculateOffset(anchorElement)
)
} else {
window.document.documentElement.scroll(
0,
calculateOffset(anchorElement)
)
setShouldScrollToInitialPosition(false)
if (props.initialReadingProgress && props.initialReadingProgress >= 98) {
return
}
const anchorElement = props.highlightHref.current
? document.querySelector(
`[omnivore-highlight-id="${props.highlightHref.current}"]`
)
: document.querySelector(
`[data-omnivore-anchor-idx='${props.initialAnchorIndex.toString()}']`
)
if (anchorElement) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const calculateOffset = (obj: any): number => {
let offset = 0
if (obj.offsetParent) {
do {
offset += obj.offsetTop
} while ((obj = obj.offsetParent))
return offset
}
return 0
}
const calculatedOffset = calculateOffset(anchorElement)
if (props.scrollElementRef.current) {
props.scrollElementRef.current?.scroll(0, calculatedOffset - 100)
} else {
window.document.documentElement.scroll(0, calculatedOffset - 100)
}
}
}
}, [
props.highlightReady,
props.scrollElementRef,
props.initialAnchorIndex,
props.initialReadingProgress,
props.scrollElementRef,
shouldScrollToInitialPosition,
])

View File

@ -6,7 +6,7 @@ import { ArticleSubtitle } from './../../patterns/ArticleSubtitle'
import { theme, ThemeId } from './../../tokens/stitches.config'
import { HighlightsLayer } from '../../templates/article/HighlightsLayer'
import { Button } from '../../elements/Button'
import { MutableRefObject, useEffect, useState } from 'react'
import { MutableRefObject, useEffect, useState, useRef } from 'react'
import { ReportIssuesModal } from './ReportIssuesModal'
import { reportIssueMutation } from '../../../lib/networking/mutations/reportIssueMutation'
import { ArticleHeaderToolbar } from './ArticleHeaderToolbar'
@ -15,6 +15,7 @@ import { updateThemeLocally } from '../../../lib/themeUpdater'
import { ArticleMutations } from '../../../lib/articleActions'
import { LabelChip } from '../../elements/LabelChip'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { HighlightLocation, makeHighlightStartEndOffset } from '../../../lib/highlights/highlightGenerator'
type ArticleContainerProps = {
article: ArticleAttributes
@ -36,6 +37,11 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
const [showShareModal, setShowShareModal] = useState(false)
const [showReportIssuesModal, setShowReportIssuesModal] = useState(false)
const [fontSize, setFontSize] = useState(props.fontSize ?? 20)
const highlightHref = useRef(window.location.hash ? window.location.hash.split('#')[1] : null)
const [highlightReady, setHighlightReady] = useState(false)
const [highlightLocations, setHighlightLocations] = useState<
HighlightLocation[]
>([])
const updateFontSize = async (newFontSize: number) => {
if (fontSize !== newFontSize) {
@ -48,6 +54,21 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
updateFontSize(props.fontSize ?? 20)
}, [props.fontSize])
// Load the highlights
useEffect(() => {
const res: HighlightLocation[] = []
props.article.highlights.forEach((highlight) => {
try {
const offset = makeHighlightStartEndOffset(highlight)
res.push(offset)
} catch (err) {
console.error(err)
}
})
setHighlightLocations(res)
setHighlightReady(true)
}, [props.article.highlights, setHighlightLocations])
// Listen for font size and color mode change events sent from host apps (ios, macos...)
useEffect(() => {
const increaseFontSize = async () => {
@ -158,6 +179,8 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
)}
</VStack>
<Article
highlightReady={highlightReady}
highlightHref={highlightHref}
articleId={props.article.id}
content={props.article.content}
initialAnchorIndex={props.article.readingProgressAnchorIndex}
@ -182,6 +205,7 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
<Box css={{ height: '100px' }} />
</Box>
<HighlightsLayer
highlightLocations={highlightLocations}
highlights={props.article.highlights}
articleTitle={props.article.title}
articleAuthor={props.article.author ?? ''}

View File

@ -29,6 +29,7 @@ type HighlightsLayerProps = {
highlightsBaseURL: string
setShowHighlightsModal: React.Dispatch<React.SetStateAction<boolean>>
articleMutations: ArticleMutations
highlightLocations: HighlightLocation[]
}
type HighlightModalAction = 'none' | 'addComment' | 'share'
@ -49,9 +50,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
const [highlightModalAction, setHighlightModalAction] =
useState<HighlightActionProps>({ highlightModalAction: 'none' })
const [highlightLocations, setHighlightLocations] = useState<
HighlightLocation[]
>([])
const focusedHighlightMousePos = useRef({ pageX: 0, pageY: 0 })
const [focusedHighlight, setFocusedHighlight] = useState<
@ -59,26 +57,12 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
>(undefined)
const [selectionData, setSelectionData] = useSelection(
highlightLocations,
props.highlightLocations,
false //noteModal.open,
)
const canShareNative = useCanShareNative()
// Load the highlights
useEffect(() => {
const res: HighlightLocation[] = []
highlights.forEach((highlight) => {
try {
const offset = makeHighlightStartEndOffset(highlight)
res.push(offset)
} catch (err) {
console.error(err)
}
})
setHighlightLocations(res)
}, [highlights, setHighlightLocations])
const removeHighlightCallback = useCallback(
async (id?: string) => {
const highlightId = id || focusedHighlight?.id
@ -89,7 +73,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
if (didDeleteHighlight) {
removeHighlights(
highlights.map(($0) => $0.id),
highlightLocations
props.highlightLocations
)
setHighlights(highlights.filter(($0) => $0.id !== highlightId))
setFocusedHighlight(undefined)
@ -97,16 +81,16 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
console.error('Failed to delete highlight')
}
},
[focusedHighlight, highlights, highlightLocations]
[focusedHighlight, highlights, props.highlightLocations]
)
const updateHighlightsCallback = useCallback(
(highlight: Highlight) => {
removeHighlights([highlight.id], highlightLocations)
removeHighlights([highlight.id], props.highlightLocations)
const keptHighlights = highlights.filter(($0) => $0.id !== highlight.id)
setHighlights([...keptHighlights, highlight])
},
[highlights, highlightLocations]
[highlights, props.highlightLocations]
)
const handleNativeShare = useCallback(
@ -159,7 +143,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
selection: selection,
articleId: props.articleId,
existingHighlights: highlights,
highlightStartEndOffsets: highlightLocations,
highlightStartEndOffsets: props.highlightLocations,
annotation: note,
}, props.articleMutations)
@ -214,10 +198,22 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
selectionData,
setSelectionData,
canShareNative,
highlightLocations,
props.highlightLocations,
]
)
const scrollToHighlight = (id: string) => {
const foundElement = document.querySelector(`[omnivore-highlight-id="${id}"]`)
if(foundElement){
foundElement.scrollIntoView({
block: 'center',
behavior: 'smooth'
})
window.location.hash = `#${id}`
props.setShowHighlightsModal(false)
}
}
// Detect mouseclick on a highlight -- call `setFocusedHighlight` when highlight detected
const handleClickHighlight = useCallback(
(event: MouseEvent) => {
@ -261,7 +257,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
})
} else setFocusedHighlight(undefined)
},
[highlights, highlightLocations]
[highlights, props.highlightLocations]
)
useEffect(() => {
@ -469,9 +465,10 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
if (props.showHighlightsModal) {
return (
<HighlightsModal
highlights={highlights}
onOpenChange={() => props.setShowHighlightsModal(false)}
deleteHighlightAction={(highlightId: string) => {
highlights={highlights}
onOpenChange={() => props.setShowHighlightsModal(false)}
scrollToHighlight={scrollToHighlight}
deleteHighlightAction={(highlightId: string) => {
removeHighlightCallback(highlightId)
}}
/>

View File

@ -19,6 +19,7 @@ import { Pen, Trash } from 'phosphor-react'
type HighlightsModalProps = {
highlights: Highlight[]
scrollToHighlight?: (arg: string) => void;
deleteHighlightAction?: (highlightId: string) => void
onOpenChange: (open: boolean) => void
}
@ -60,6 +61,7 @@ export function HighlightsModal(props: HighlightsModalProps): JSX.Element {
key={highlight.id}
highlight={highlight}
showDelete={!!props.deleteHighlightAction}
scrollToHighlight={props.scrollToHighlight}
deleteHighlightAction={() => {
if (props.deleteHighlightAction) {
props.deleteHighlightAction(highlight.id)
@ -82,6 +84,7 @@ export function HighlightsModal(props: HighlightsModalProps): JSX.Element {
type ModalHighlightViewProps = {
highlight: Highlight
showDelete: boolean
scrollToHighlight?: (arg: string) => void;
deleteHighlightAction: () => void
}
@ -156,7 +159,7 @@ function ModalHighlightView(props: ModalHighlightViewProps): JSX.Element {
return (
<>
<VStack>
<HighlightView highlight={props.highlight} />
<HighlightView scrollToHighlight={props.scrollToHighlight} highlight={props.highlight} />
{props.highlight.annotation && !isEditing ? (
<StyledText css={{ px: '24px' }}>{props.highlight.annotation}</StyledText>
) : null}

View File

@ -40,7 +40,7 @@ import { Label } from '../../../lib/networking/fragments/labelFragment'
import { isVipUser } from '../../../lib/featureFlag'
import { EmptyLibrary } from './EmptyLibrary'
import TopBarProgress from 'react-topbar-progress-indicator'
import { State } from '../../../lib/networking/fragments/articleFragment'
import { State, PageType } from '../../../lib/networking/fragments/articleFragment'
export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT'
@ -53,7 +53,7 @@ const timeZoneHourDiff = -new Date().getTimezoneOffset() / 60
const SAVED_SEARCHES: Record<string, string> = {
Inbox: `in:inbox`,
'Read Later': `in:inbox -label:Newsletter`,
Highlighted: `in:inbox has:highlights`,
Highlights: `type:highlights`,
Today: `in:inbox saved:${
new Date(new Date().getTime() - 24 * 3600000).toISOString().split('T')[0]
}Z${timeZoneHourDiff.toLocaleString('en-US', {
@ -112,23 +112,23 @@ export function HomeFeedContainer(props: HomeFeedContainerProps): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [setQueryInputs, router.isReady, router.query])
const { articlesPages, size, setSize, isValidating, performActionOnItem } =
const { itemsPages, size, setSize, isValidating, performActionOnItem } =
useGetLibraryItemsQuery(queryInputs)
const hasMore = useMemo(() => {
if (!articlesPages) {
if (!itemsPages) {
return false
}
return articlesPages[articlesPages.length - 1].articles.pageInfo.hasNextPage
}, [articlesPages])
return itemsPages[itemsPages.length - 1].search.pageInfo.hasNextPage
}, [itemsPages])
const libraryItems = useMemo(() => {
const items =
articlesPages?.flatMap((ad) => {
return ad.articles.edges
itemsPages?.flatMap((ad) => {
return ad.search.edges
}) || []
return items
}, [articlesPages, performActionOnItem])
}, [itemsPages, performActionOnItem])
const handleFetchMore = useCallback(() => {
if (isValidating || !hasMore) {
@ -262,7 +262,8 @@ export function HomeFeedContainer(props: HomeFeedContainerProps): JSX.Element {
if (item.node.state === State.PROCESSING) {
router.push(`/${username}/links/${item.node.id}`)
} else {
router.push(`/${username}/${item.node.slug}`)
const dl = item.node.pageType === PageType.HIGHLIGHTS ? `#${item.node.id}` : ''
router.push(`/${username}/${item.node.slug}` + dl)
}
}
break
@ -440,8 +441,8 @@ export function HomeFeedContainer(props: HomeFeedContainerProps): JSX.Element {
setSize(size + 1)
}}
hasMore={hasMore}
hasData={!!articlesPages}
totalItems={articlesPages?.[0].articles.pageInfo.totalCount || 0}
hasData={!!itemsPages}
totalItems={itemsPages?.[0].search.pageInfo.totalCount || 0}
isValidating={isValidating}
shareTarget={shareTarget}
setShareTarget={setShareTarget}

View File

@ -1,10 +1,9 @@
import { ReactNode, useEffect } from 'react'
import { useState, useRef } from 'react'
import { ReactNode, useEffect, useRef, useState } from 'react'
import { StyledText } from '../../elements/StyledText'
import { Box, HStack, VStack } from '../../elements/LayoutPrimitives'
import { SearchIcon } from '../../elements/images/SearchIcon'
import { theme } from '../../tokens/stitches.config'
import { DropdownOption, Dropdown } from '../../elements/DropdownElements'
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
import { FormInput } from '../../elements/FormElements'
import { searchBarCommands } from '../../../lib/keyboardShortcuts/navigationShortcuts'
import { useKeyboardShortcuts } from '../../../lib/keyboardShortcuts/useKeyboardShortcuts'
@ -16,7 +15,19 @@ type LibrarySearchBarProps = {
applySearchQuery: (searchQuery: string) => void
}
type LibraryFilter = 'in:inbox' | 'in:all' | 'in:archive' | 'type:file'
type LibraryFilter =
| 'in:inbox'
| 'in:all'
| 'in:archive'
| 'type:file'
| 'type:highlights'
| `saved:${string}`
| `sort:updated`
// get last week's date
const recentlySavedStartDate = new Date(
new Date().getTime() - 7 * 24 * 60 * 60 * 1000
).toLocaleDateString('en-US')
const FOCUSED_BOXSHADOW = '0px 0px 2px 2px rgba(255, 234, 159, 0.56)'
@ -153,6 +164,21 @@ export function DropdownFilterMenu(
title="Files"
hideSeparator
/>
<DropdownOption
onSelect={() => props.onFilterChange('type:highlights')}
title="Highlights"
hideSeparator
/>
<DropdownOption
onSelect={() => props.onFilterChange(`saved:${recentlySavedStartDate}`)}
title="Recently Saved"
hideSeparator
/>
<DropdownOption
onSelect={() => props.onFilterChange(`sort:updated`)}
title="Recently Read"
hideSeparator
/>
</Dropdown>
)
}

View File

@ -30,6 +30,16 @@ export enum State {
FAILED = 'FAILED',
}
export enum PageType {
ARTICLE = 'ARTICLE',
BOOK = 'BOOK',
FILE = 'FILE',
PROFILE = 'PROFILE',
WEBSITE = 'WEBSITE',
HIGHLIGHTS = 'HIGHLIGHTS',
UNKNOWN = 'UNKNOWN',
}
export type ArticleFragmentData = {
id: string
title: string

View File

@ -1,7 +1,8 @@
import { gql } from 'graphql-request'
import useSWRInfinite from 'swr/infinite'
import { gqlFetcher } from '../networkHelpers'
import type { ArticleFragmentData } from '../fragments/articleFragment'
import type { ArticleFragmentData, PageType, State } from '../fragments/articleFragment'
import { ContentReader } from '../fragments/articleFragment'
import { setLinkArchivedMutation } from '../mutations/setLinkArchivedMutation'
import { deleteLinkMutation } from '../mutations/deleteLinkMutation'
import { articleReadingProgressMutation } from '../mutations/articleReadingProgressMutation'
@ -16,8 +17,8 @@ export type LibraryItemsQueryInput = {
}
type LibraryItemsQueryResponse = {
articlesPages?: LibraryItemsData[]
articlesDataError?: unknown
itemsPages?: LibraryItemsData[]
itemsDataError?: unknown
isLoading: boolean
isValidating: boolean
size: number
@ -36,7 +37,7 @@ type LibraryItemAction =
| 'refresh'
export type LibraryItemsData = {
articles: LibraryItems
search: LibraryItems
}
export type LibraryItems = {
@ -50,12 +51,30 @@ export type LibraryItem = {
node: LibraryItemNode
}
export type LibraryItemNode = ArticleFragmentData & {
description?: string
hasContent: boolean
export type LibraryItemNode = {
id: string
title: string
url: string
author?: string
image?: string
createdAt: string
publishedAt?: string
contentReader?: ContentReader
originalArticleUrl: string
sharedComment?: string
readingProgressPercent: number
readingProgressAnchorIndex: number
slug: string
isArchived: boolean
description: string
ownedByViewer: boolean
uploadFileId: string
labels?: Label[]
pageId: string
shortId: string
quote: string
annotation: string
state: State
pageType: PageType
}
export type PageInfo = {
@ -66,31 +85,6 @@ export type PageInfo = {
totalCount: number
}
const libraryItemFragment = gql`
fragment ArticleFields on Article {
id
title
url
author
image
savedAt
createdAt
publishedAt
contentReader
originalArticleUrl
readingProgressPercent
readingProgressAnchorIndex
slug
isArchived
description
linkId
state
labels {
...LabelFields
}
}
`
export function useGetLibraryItemsQuery({
limit,
sortDescending,
@ -98,27 +92,38 @@ export function useGetLibraryItemsQuery({
cursor,
}: LibraryItemsQueryInput): LibraryItemsQueryResponse {
const query = gql`
query GetArticles(
$sharedOnly: Boolean
$sort: SortParams
$after: String
$first: Int
$query: String
) {
articles(
sharedOnly: $sharedOnly
sort: $sort
first: $first
after: $after
query: $query
includePending: true
) {
... on ArticlesSuccess {
query Search($after: String, $first: Int, $query: String) {
search(first: $first, after: $after, query: $query) {
... on SearchSuccess {
edges {
cursor
node {
...ArticleFields
id
title
slug
url
pageType
contentReader
createdAt
isArchived
readingProgressPercent
readingProgressAnchorIndex
author
image
description
publishedAt
ownedByViewer
originalArticleUrl
uploadFileId
labels {
id
name
color
}
pageId
shortId
quote
annotation
}
}
pageInfo {
@ -129,21 +134,14 @@ export function useGetLibraryItemsQuery({
totalCount
}
}
... on ArticlesError {
... on SearchError {
errorCodes
}
}
}
${libraryItemFragment}
${labelFragment}
`
const variables = {
sharedOnly: false,
sort: {
order: sortDescending ? 'DESCENDING' : 'ASCENDING',
by: 'UPDATED_TIME',
},
after: cursor,
first: limit,
query: searchQuery,
@ -162,12 +160,10 @@ export function useGetLibraryItemsQuery({
limit,
sortDescending,
searchQuery,
pageIndex === 0
? undefined
: previousResult.articles.pageInfo.endCursor,
pageIndex === 0 ? undefined : previousResult.search.pageInfo.endCursor,
]
},
(_query, _l, _s, _sq, cursor: string) => {
(_query, _l, _s, _sq, cursor) => {
return gqlFetcher(query, { ...variables, after: cursor }, true)
},
{ revalidateFirstPage: false }
@ -182,7 +178,7 @@ export function useGetLibraryItemsQuery({
// the response in the case of an error.
if (!error && responsePages) {
const errors = responsePages.filter(
(d) => d.articles.errorCodes && d.articles.errorCodes.length > 0
(d) => d.search.errorCodes && d.search.errorCodes.length > 0
)
if (errors?.length > 0) {
responseError = errors
@ -202,13 +198,13 @@ export function useGetLibraryItemsQuery({
if (!responsePages) {
return
}
for (const articlesData of responsePages) {
const itemIndex = articlesData.articles.edges.indexOf(item)
for (const searchResults of responsePages) {
const itemIndex = searchResults.search.edges.indexOf(item)
if (itemIndex !== -1) {
if (typeof mutatedItem === 'undefined') {
articlesData.articles.edges.splice(itemIndex, 1)
searchResults.search.edges.splice(itemIndex, 1)
} else {
articlesData.articles.edges[itemIndex] = mutatedItem
searchResults.search.edges[itemIndex] = mutatedItem
}
break
}
@ -313,8 +309,8 @@ export function useGetLibraryItemsQuery({
return {
isValidating,
articlesPages: responsePages || undefined,
articlesDataError: responseError,
itemsPages: responsePages || undefined,
itemsDataError: responseError,
isLoading: !error && !data,
performActionOnItem,
size,

View File

@ -0,0 +1,71 @@
import { ComponentStory, ComponentMeta } from '@storybook/react'
import { HighlightItemCard, HighlightItemCardProps } from '../components/patterns/LibraryCards/HighlightItemCard'
import { updateThemeLocally } from '../lib/themeUpdater'
import { ThemeId } from '../components/tokens/stitches.config'
import { PageType, State } from '../lib/networking/fragments/articleFragment'
export default {
title: 'Components/HighlightItemCard',
component: HighlightItemCard,
argTypes: {
item: {
description: 'The highlight.',
},
handleAction: {
description: 'Action that fires on click.'
}
}
} as ComponentMeta<typeof HighlightItemCard>
const highlight: HighlightItemCardProps = {
handleAction: () => console.log('Handling Action'),
item:{
id: "nnnnn",
shortId: "shortId",
quote: "children not only participate in herding work, but are also encouraged to act independently in most other areas of life. They have a say in deciding when to eat, when to sleep, and what to wear, even at temperatures of -30C (-22F).",
annotation: "Okay… this is wild! I love this independence. Wondering how I can reponsibly instill this type of indepence in my own kids…",
createdAt: '',
description: '',
isArchived: false,
originalArticleUrl: 'https://example.com',
ownedByViewer: true,
pageId: '1',
readingProgressAnchorIndex: 12,
readingProgressPercent: 50,
slug: 'slug',
title: "This is a title",
uploadFileId: '1',
url: 'https://example.com',
author: 'Author',
image: 'https://logos-world.net/wp-content/uploads/2021/11/Unity-New-Logo.png',
state: State.SUCCEEDED,
pageType: PageType.HIGHLIGHTS,
},
}
const Template = (props: HighlightItemCardProps) => <HighlightItemCard {...props} />
export const LightHighlightItemCard: ComponentStory<
typeof HighlightItemCard
> = (args: any) => {
updateThemeLocally(ThemeId.Light)
return (
<Template {...args}/>
)
}
export const DarkHighlightItemCard: ComponentStory<
typeof HighlightItemCard
> = (args: any) => {
updateThemeLocally(ThemeId.Dark)
return (
<Template {...args}/>
)
}
LightHighlightItemCard.args = {
...highlight
}
DarkHighlightItemCard.args = {
...highlight
}