mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Better handlding of deleting highlights from the library highlights view
This commit is contained in:
parent
49e1947095
commit
f61efd2d3e
4 changed files with 157 additions and 74 deletions
|
|
@ -18,6 +18,7 @@ export function EmptyLibrary(props: EmptyLibraryProps): JSX.Element {
|
|||
color: '$grayTextContrast',
|
||||
textAlign: 'center',
|
||||
paddingTop: '88px',
|
||||
flex: '1',
|
||||
}}
|
||||
>
|
||||
<Book size={44} color={theme.colors.grayTextContrast.toString()} />
|
||||
|
|
|
|||
|
|
@ -21,12 +21,15 @@ import {
|
|||
} from '../../elements/LayoutPrimitives'
|
||||
import { StyledText } from '../../elements/StyledText'
|
||||
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
|
||||
import { theme } from '../../tokens/stitches.config'
|
||||
import { SetLabelsModal } from '../article/SetLabelsModal'
|
||||
|
||||
type HighlightItemProps = {
|
||||
highlight: Highlight
|
||||
viewer: UserBasicData | undefined
|
||||
item: LibraryItemNode
|
||||
|
||||
deleteHighlight: (item: LibraryItemNode, highlight: Highlight) => void
|
||||
}
|
||||
|
||||
const StyledQuote = styled(Blockquote, {
|
||||
|
|
@ -140,10 +143,6 @@ export function HighlightItem(props: HighlightItemProps): JSX.Element {
|
|||
css={{
|
||||
marginLeft: 'auto',
|
||||
width: '20px',
|
||||
visibility: hover ? 'unset' : 'hidden',
|
||||
'@media (hover: none)': {
|
||||
visibility: 'unset',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<HighlightsMenu
|
||||
|
|
@ -163,6 +162,7 @@ export function HighlightItem(props: HighlightItemProps): JSX.Element {
|
|||
)
|
||||
if (result) {
|
||||
showSuccessToast('Highlight deleted')
|
||||
props.deleteHighlight(props.item, props.highlight)
|
||||
} else {
|
||||
showErrorToast('Error deleting highlight')
|
||||
}
|
||||
|
|
@ -207,14 +207,6 @@ export function HighlightsMenu(props: HighlightsMenuProps): JSX.Element {
|
|||
})()
|
||||
}, [props.highlight])
|
||||
|
||||
const exportHighlight = useCallback(() => {
|
||||
;(async () => {
|
||||
const markdown = highlightAsMarkdown(props.highlight)
|
||||
await navigator.clipboard.writeText(markdown)
|
||||
showSuccessToast('Highlight copied')
|
||||
})()
|
||||
}, [props.highlight])
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
triggerElement={
|
||||
|
|
@ -231,7 +223,11 @@ export function HighlightsMenu(props: HighlightsMenuProps): JSX.Element {
|
|||
},
|
||||
}}
|
||||
>
|
||||
<DotsThreeVertical size={20} color="#EBEBEB" weight="bold" />
|
||||
<DotsThreeVertical
|
||||
size={20}
|
||||
color={theme.colors.thTextContrast2.toString()}
|
||||
weight="bold"
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
|
|
@ -249,7 +245,7 @@ export function HighlightsMenu(props: HighlightsMenuProps): JSX.Element {
|
|||
/>
|
||||
<DropdownOption
|
||||
onSelect={() => {
|
||||
exportHighlight()
|
||||
props.setShowConfirmDeleteHighlightId(props.highlight.id)
|
||||
}}
|
||||
title="Delete"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import { HighlighterCircle } from 'phosphor-react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useCallback, useEffect, useReducer, useState } from 'react'
|
||||
import { Toaster } from 'react-hot-toast'
|
||||
import { LibraryItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
|
||||
import {
|
||||
LibraryItem,
|
||||
LibraryItemNode,
|
||||
} from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
|
||||
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
|
||||
|
|
@ -14,6 +18,8 @@ import {
|
|||
timeAgo,
|
||||
} from '../../patterns/LibraryCards/LibraryCardStyles'
|
||||
import { LibraryHighlightGridCard } from '../../patterns/LibraryCards/LibraryHighlightGridCard'
|
||||
import { EmptyHighlights } from './EmptyHighlights'
|
||||
import { HEADER_HEIGHT, MOBILE_HEADER_HEIGHT } from './HeaderSpacer'
|
||||
import { HighlightItem, highlightsAsMarkdown } from './HighlightItem'
|
||||
|
||||
type HighlightItemsLayoutProps = {
|
||||
|
|
@ -30,14 +36,74 @@ export function HighlightItemsLayout(
|
|||
undefined
|
||||
)
|
||||
|
||||
const listReducer = (
|
||||
state: LibraryItem[],
|
||||
action: {
|
||||
type: string
|
||||
itemId?: string
|
||||
highlightId?: string
|
||||
items?: LibraryItem[]
|
||||
}
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case 'RESET':
|
||||
return action.items ?? []
|
||||
case 'REMOVE_HIGHLIGHT':
|
||||
const item = state.find((li) => li.node.id === action.itemId)
|
||||
if (item && item.node.highlights) {
|
||||
item.node.highlights = item.node.highlights.filter(
|
||||
(h) => h.id !== action.highlightId
|
||||
)
|
||||
}
|
||||
return state.filter(
|
||||
(item) => item.node.highlights && item.node.highlights.length > 0
|
||||
)
|
||||
default:
|
||||
throw new Error()
|
||||
}
|
||||
}
|
||||
|
||||
const [items, dispatchList] = useReducer(listReducer, [])
|
||||
|
||||
function handleDelete(item: LibraryItemNode, highlight: Highlight) {
|
||||
dispatchList({
|
||||
type: 'REMOVE_HIGHLIGHT',
|
||||
itemId: item.id,
|
||||
highlightId: highlight.id,
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
dispatchList({
|
||||
type: 'RESET',
|
||||
items: props.items,
|
||||
})
|
||||
}, [props.items])
|
||||
|
||||
useEffect(() => {
|
||||
// Only set the current item on larger screens
|
||||
if (window.innerWidth >= 992 /* lgDown */) {
|
||||
if (!currentItem && props.items.length > 0) {
|
||||
setCurrentItem(props.items[0])
|
||||
if (!currentItem && items.length > 0) {
|
||||
setCurrentItem(items[0])
|
||||
}
|
||||
}
|
||||
}, [currentItem, setCurrentItem, props.items])
|
||||
}, [currentItem, setCurrentItem, items])
|
||||
|
||||
if (items.length < 1) {
|
||||
return (
|
||||
<Box
|
||||
css={{
|
||||
width: '100%',
|
||||
height: `calc(100vh - ${HEADER_HEIGHT})`,
|
||||
'@xlgDown': {
|
||||
height: `calc(100vh - ${MOBILE_HEADER_HEIGHT})`,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<EmptyHighlights />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -45,6 +111,7 @@ export function HighlightItemsLayout(
|
|||
css={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
bg: '$thBackground2',
|
||||
}}
|
||||
distribution="start"
|
||||
|
|
@ -63,59 +130,30 @@ export function HighlightItemsLayout(
|
|||
distribution="start"
|
||||
alignment="start"
|
||||
>
|
||||
<HStack
|
||||
css={{
|
||||
width: 'calc(100% - 35px)',
|
||||
height: '55px',
|
||||
mx: '20px',
|
||||
borderBottom: '1px solid $thBorderColor',
|
||||
}}
|
||||
alignment="center"
|
||||
distribution="start"
|
||||
>
|
||||
{/* <Box
|
||||
css={{
|
||||
display: 'flex',
|
||||
height: '20px',
|
||||
width: '20px',
|
||||
marginLeft: 'auto',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: '1000px',
|
||||
'&:hover': {
|
||||
bg: '#898989',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DotsThreeVertical size={200} color="#898989" weight="bold" />
|
||||
</Box> */}
|
||||
</HStack>
|
||||
{props.items.map((linkedItem) => (
|
||||
<Box
|
||||
className="linkedItemCard"
|
||||
data-testid="linkedItemCard"
|
||||
id={linkedItem.node.id}
|
||||
tabIndex={0}
|
||||
key={linkedItem.node.id}
|
||||
css={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
px: '15px',
|
||||
}}
|
||||
onClick={(event) => {
|
||||
setCurrentItem(linkedItem)
|
||||
event.preventDefault()
|
||||
}}
|
||||
>
|
||||
{props.viewer && (
|
||||
<LibraryItemCard
|
||||
item={linkedItem}
|
||||
viewer={props.viewer}
|
||||
selected={currentItem?.node.id == linkedItem.node.id}
|
||||
/>
|
||||
)}
|
||||
{items.length > 0 ? (
|
||||
<>
|
||||
<HStack
|
||||
css={{
|
||||
width: 'calc(100% - 35px)',
|
||||
height: '55px',
|
||||
mx: '20px',
|
||||
borderBottom: '1px solid $thBorderColor',
|
||||
}}
|
||||
alignment="center"
|
||||
distribution="start"
|
||||
></HStack>
|
||||
<LibraryItemsList
|
||||
items={items}
|
||||
viewer={props.viewer}
|
||||
currentItem={currentItem}
|
||||
setCurrentItem={setCurrentItem}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Box css={{ width: '100%', height: '200px' }}>
|
||||
No highlights found
|
||||
</Box>
|
||||
))}
|
||||
)}
|
||||
</VStack>
|
||||
{currentItem && (
|
||||
<>
|
||||
|
|
@ -130,7 +168,11 @@ export function HighlightItemsLayout(
|
|||
},
|
||||
}}
|
||||
>
|
||||
<HighlightList item={currentItem} viewer={props.viewer} />
|
||||
<HighlightList
|
||||
item={currentItem}
|
||||
viewer={props.viewer}
|
||||
deleteHighlight={handleDelete}
|
||||
/>
|
||||
</SpanBox>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -139,6 +181,47 @@ export function HighlightItemsLayout(
|
|||
)
|
||||
}
|
||||
|
||||
type LibraryItemsListProps = {
|
||||
items: LibraryItem[]
|
||||
viewer: UserBasicData | undefined
|
||||
|
||||
currentItem: LibraryItem | undefined
|
||||
setCurrentItem: (item: LibraryItem | undefined) => void
|
||||
}
|
||||
|
||||
function LibraryItemsList(props: LibraryItemsListProps): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
{props.items.map((linkedItem) => (
|
||||
<Box
|
||||
className="linkedItemCard"
|
||||
data-testid="linkedItemCard"
|
||||
id={linkedItem.node.id}
|
||||
tabIndex={0}
|
||||
key={linkedItem.node.id}
|
||||
css={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
px: '15px',
|
||||
}}
|
||||
onClick={(event) => {
|
||||
props.setCurrentItem(linkedItem)
|
||||
event.preventDefault()
|
||||
}}
|
||||
>
|
||||
{props.viewer && (
|
||||
<LibraryItemCard
|
||||
item={linkedItem}
|
||||
viewer={props.viewer}
|
||||
selected={props.currentItem?.node.id == linkedItem.node.id}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
type HighlightTitleCardProps = {
|
||||
item: LibraryItem
|
||||
viewer: UserBasicData
|
||||
|
|
@ -238,6 +321,8 @@ function HighlightTitleCard(props: HighlightTitleCardProps): JSX.Element {
|
|||
type HighlightListProps = {
|
||||
item: LibraryItem
|
||||
viewer: UserBasicData | undefined
|
||||
|
||||
deleteHighlight: (item: LibraryItemNode, highlight: Highlight) => void
|
||||
}
|
||||
|
||||
function HighlightList(props: HighlightListProps): JSX.Element {
|
||||
|
|
@ -288,7 +373,7 @@ function HighlightList(props: HighlightListProps): JSX.Element {
|
|||
fontSize: '15px',
|
||||
fontFamily: '$display',
|
||||
width: '100%',
|
||||
color: '$thTextContrast2',
|
||||
color: 'thTextContrast2',
|
||||
}}
|
||||
>
|
||||
HIGHLIGHTS
|
||||
|
|
@ -309,6 +394,7 @@ function HighlightList(props: HighlightListProps): JSX.Element {
|
|||
viewer={props.viewer}
|
||||
item={props.item.node}
|
||||
highlight={highlight}
|
||||
deleteHighlight={props.deleteHighlight}
|
||||
/>
|
||||
))}
|
||||
</VStack>
|
||||
|
|
|
|||
|
|
@ -686,7 +686,7 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
|
|||
setShowFilterMenu={setShowFilterMenu}
|
||||
/>
|
||||
|
||||
{props.mode == 'highlights' && (
|
||||
{!props.isValidating && props.mode == 'highlights' && (
|
||||
<HighlightItemsLayout
|
||||
gridContainerRef={props.gridContainerRef}
|
||||
items={props.items}
|
||||
|
|
|
|||
Loading…
Reference in a new issue