diff --git a/packages/web/components/elements/StyledText.tsx b/packages/web/components/elements/StyledText.tsx index f5113ffb3..55ef94b5e 100644 --- a/packages/web/components/elements/StyledText.tsx +++ b/packages/web/components/elements/StyledText.tsx @@ -177,3 +177,7 @@ export const StyledImg = styled('img', { export const StyledAnchor = styled('a', { textDecoration: 'none' }) + +export const StyledMark = styled('mark', { + +}) \ No newline at end of file diff --git a/packages/web/components/patterns/HighlightView.tsx b/packages/web/components/patterns/HighlightView.tsx index 2534048ca..e6c5d631d 100644 --- a/packages/web/components/patterns/HighlightView.tsx +++ b/packages/web/components/patterns/HighlightView.tsx @@ -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 { {annotation} ) } - + { + if (props.scrollToHighlight) { + props.scrollToHighlight(props.highlight.id) + } + }}> {props.highlight.prefix} {lines.map((line: string, index: number) => ( diff --git a/packages/web/components/patterns/LibraryCards/HighlightItemCard.tsx b/packages/web/components/patterns/LibraryCards/HighlightItemCard.tsx new file mode 100644 index 000000000..f3f685ab7 --- /dev/null +++ b/packages/web/components/patterns/LibraryCards/HighlightItemCard.tsx @@ -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 { + handleAction: (action: LinkedItemCardAction) => void +} + +export const PreviewImage = styled('img', { + objectFit: 'cover', + cursor: 'pointer', +}) + +export function HighlightItemCard(props: HighlightItemCardProps): JSX.Element { + return ( + { + props.handleAction('showDetail') + }} + > + + + {props.item.quote} + + + + {props.item.image && ( + { + ;(e.target as HTMLElement).style.display = 'none' + }} + /> + )} + + {props.item.title + .substring(0, 50) + .concat(props.item.title.length > 50 ? '...' : '')} + + + + ) +} diff --git a/packages/web/components/patterns/LibraryCards/LinkedItemCard.tsx b/packages/web/components/patterns/LibraryCards/LinkedItemCard.tsx index 12a4a4be7..0b201184d 100644 --- a/packages/web/components/patterns/LibraryCards/LinkedItemCard.tsx +++ b/packages/web/components/patterns/LibraryCards/LinkedItemCard.tsx @@ -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 + } if (props.layout == 'LIST_LAYOUT') { return } else { diff --git a/packages/web/components/templates/article/Article.tsx b/packages/web/components/templates/article/Article.tsx index 9c6382857..d4f931565 100644 --- a/packages/web/components/templates/article/Article.tsx +++ b/packages/web/components/templates/article/Article.tsx @@ -21,8 +21,10 @@ import { ArticleMutations } from '../../../lib/articleActions' export type ArticleProps = { articleId: string content: string + highlightReady: boolean initialAnchorIndex: number initialReadingProgress?: number + highlightHref: MutableRefObject scrollElementRef: MutableRefObject 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, ]) diff --git a/packages/web/components/templates/article/ArticleContainer.tsx b/packages/web/components/templates/article/ArticleContainer.tsx index 1ef48a9f7..78c7371b4 100644 --- a/packages/web/components/templates/article/ArticleContainer.tsx +++ b/packages/web/components/templates/article/ArticleContainer.tsx @@ -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 { )}
> articleMutations: ArticleMutations + highlightLocations: HighlightLocation[] } type HighlightModalAction = 'none' | 'addComment' | 'share' @@ -49,9 +50,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { const [highlightModalAction, setHighlightModalAction] = useState({ 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 ( props.setShowHighlightsModal(false)} - deleteHighlightAction={(highlightId: string) => { + highlights={highlights} + onOpenChange={() => props.setShowHighlightsModal(false)} + scrollToHighlight={scrollToHighlight} + deleteHighlightAction={(highlightId: string) => { removeHighlightCallback(highlightId) }} /> diff --git a/packages/web/components/templates/article/HighlightsModal.tsx b/packages/web/components/templates/article/HighlightsModal.tsx index d31557c4b..9f3ffde6c 100644 --- a/packages/web/components/templates/article/HighlightsModal.tsx +++ b/packages/web/components/templates/article/HighlightsModal.tsx @@ -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 ( <> - + {props.highlight.annotation && !isEditing ? ( {props.highlight.annotation} ) : null} diff --git a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx index 916f02d05..37c97210f 100644 --- a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx +++ b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx @@ -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 = { 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} diff --git a/packages/web/components/templates/homeFeed/LibrarySearchBar.tsx b/packages/web/components/templates/homeFeed/LibrarySearchBar.tsx index 6e4f84e3b..6e08f3f33 100644 --- a/packages/web/components/templates/homeFeed/LibrarySearchBar.tsx +++ b/packages/web/components/templates/homeFeed/LibrarySearchBar.tsx @@ -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 /> + props.onFilterChange('type:highlights')} + title="Highlights" + hideSeparator + /> + props.onFilterChange(`saved:${recentlySavedStartDate}`)} + title="Recently Saved" + hideSeparator + /> + props.onFilterChange(`sort:updated`)} + title="Recently Read" + hideSeparator + /> ) } diff --git a/packages/web/lib/networking/fragments/articleFragment.ts b/packages/web/lib/networking/fragments/articleFragment.ts index 59794687b..a8cc4636a 100644 --- a/packages/web/lib/networking/fragments/articleFragment.ts +++ b/packages/web/lib/networking/fragments/articleFragment.ts @@ -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 diff --git a/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx b/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx index d710179d5..3cc484de8 100644 --- a/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx +++ b/packages/web/lib/networking/queries/useGetLibraryItemsQuery.tsx @@ -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, diff --git a/packages/web/stories/HighlightItemCard.stories.tsx b/packages/web/stories/HighlightItemCard.stories.tsx new file mode 100644 index 000000000..260338eb4 --- /dev/null +++ b/packages/web/stories/HighlightItemCard.stories.tsx @@ -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 + +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) => + +export const LightHighlightItemCard: ComponentStory< + typeof HighlightItemCard +> = (args: any) => { + updateThemeLocally(ThemeId.Light) + return ( +