Add new highlights layout

This commit is contained in:
Jackson Harper 2023-03-07 21:40:11 +08:00
parent 4b4e1a9a52
commit da03ad9bf3
6 changed files with 696 additions and 254 deletions

View file

@ -0,0 +1,20 @@
type SideBarProps = {
strokeColor: string
}
export function SideBarIcon(props: SideBarProps): JSX.Element {
return (
<svg
width="19"
height="14"
viewBox="0 0 19 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.10293 12.2383H7.3014V1.08129H6.10293V12.2383ZM4.62964 3.9129C4.74669 3.9129 4.84847 3.87289 4.93499 3.79286C5.0215 3.70812 5.06476 3.61633 5.06476 3.51747C5.06476 3.40919 5.0215 3.31739 4.93499 3.24207C4.84847 3.16204 4.74669 3.12203 4.62964 3.12203H2.99606C2.87901 3.12203 2.77723 3.16204 2.69071 3.24207C2.6042 3.31739 2.56094 3.40919 2.56094 3.51747C2.56094 3.61633 2.6042 3.70812 2.69071 3.79286C2.77723 3.87289 2.87901 3.9129 2.99606 3.9129H4.62964ZM4.62964 5.7418C4.74669 5.7418 4.84847 5.70179 4.93499 5.62176C5.0215 5.54173 5.06476 5.44758 5.06476 5.3393C5.06476 5.23574 5.0215 5.14629 4.93499 5.07097C4.84847 4.99094 4.74669 4.95093 4.62964 4.95093H2.99606C2.87901 4.95093 2.77723 4.99094 2.69071 5.07097C2.6042 5.14629 2.56094 5.23574 2.56094 5.3393C2.56094 5.44758 2.6042 5.54173 2.69071 5.62176C2.77723 5.70179 2.87901 5.7418 2.99606 5.7418H4.62964ZM4.62964 7.56364C4.74669 7.56364 4.84847 7.52598 4.93499 7.45066C5.0215 7.37063 5.06476 7.27883 5.06476 7.17526C5.06476 7.06699 5.0215 6.97519 4.93499 6.89987C4.84847 6.81984 4.74669 6.77982 4.62964 6.77982H2.99606C2.87901 6.77982 2.77723 6.81984 2.69071 6.89987C2.6042 6.97519 2.56094 7.06699 2.56094 7.17526C2.56094 7.27883 2.6042 7.37063 2.69071 7.45066C2.77723 7.52598 2.87901 7.56364 2.99606 7.56364H4.62964ZM2.68308 13.1562H15.8892C16.6933 13.1562 17.2938 12.9727 17.6907 12.6055C18.0877 12.243 18.2861 11.6969 18.2861 10.9672V2.34528C18.2861 1.6156 18.0877 1.06952 17.6907 0.707038C17.2938 0.339846 16.6933 0.15625 15.8892 0.15625H2.68308C1.8841 0.15625 1.28359 0.339846 0.881553 0.707038C0.484606 1.06952 0.286133 1.6156 0.286133 2.34528V10.9672C0.286133 11.6969 0.484606 12.243 0.881553 12.6055C1.28359 12.9727 1.8841 13.1562 2.68308 13.1562ZM2.69835 12.0194C2.31667 12.0194 2.02405 11.9276 1.82048 11.744C1.61692 11.5557 1.51514 11.2779 1.51514 10.9107V2.40177C1.51514 2.03458 1.61692 1.75918 1.82048 1.57559C2.02405 1.38728 2.31667 1.29313 2.69835 1.29313H15.8739C16.2505 1.29313 16.5406 1.38728 16.7441 1.57559C16.9528 1.75918 17.0571 2.03458 17.0571 2.40177V10.9107C17.0571 11.2779 16.9528 11.5557 16.7441 11.744C16.5406 11.9276 16.2505 12.0194 15.8739 12.0194H2.69835Z"
fill="#898989"
/>
</svg>
)
}

View file

@ -242,7 +242,7 @@ type TextEditAreaProps = {
updateHighlight: (highlight: Highlight) => void
}
const TextEditArea = (props: TextEditAreaProps): JSX.Element => {
export const TextEditArea = (props: TextEditAreaProps): JSX.Element => {
const [noteContent, setNoteContent] = useState(
props.highlight.annotation ?? ''
)

View file

@ -0,0 +1,390 @@
import dayjs from 'dayjs'
import { DotsThreeVertical, HighlighterCircle } from 'phosphor-react'
import { Fragment, useEffect, useMemo, useState } from 'react'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { LibraryItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
import { SideBarIcon } from '../../elements/images/SideBarIcon'
import { LabelChip } from '../../elements/LabelChip'
import {
Blockquote,
Box,
HStack,
SpanBox,
VStack,
} from '../../elements/LayoutPrimitives'
import { StyledText } from '../../elements/StyledText'
import { MetaStyle } from '../../patterns/LibraryCards/LibraryCardStyles'
import { styled, theme } from '../../tokens/stitches.config'
import { TextEditArea } from '../article/NotebookModal'
type HighlightItemsLayoutProps = {
items: LibraryItem[]
viewer: UserBasicData | undefined
gridContainerRef: React.RefObject<HTMLDivElement>
}
export function HighlightItemsLayout(
props: HighlightItemsLayoutProps
): JSX.Element {
const [currentItem, setCurrentItem] = useState<LibraryItem | undefined>(
undefined
)
useEffect(() => {
if (!currentItem && props.items.length > 0) {
setCurrentItem(props.items[0])
}
}, [currentItem, setCurrentItem, props.items])
return (
<HStack
css={{
width: '100%',
height: '100%',
bg: '#333333',
}}
distribution="start"
alignment="start"
>
<VStack
css={{
width: '430px',
height: '100%',
bg: '#2A2A2A',
overflowY: 'scroll',
}}
distribution="start"
alignment="start"
>
<HStack
css={{
width: 'calc(100% - 35px)',
height: '55px',
mx: '20px',
borderBottom: '1px solid $thBorderColor',
}}
alignment="center"
distribution="start"
>
<SideBarIcon strokeColor={theme.colors.thTextContrast.toString()} />
</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 && (
<HighlightTitleCard
item={linkedItem}
viewer={props.viewer}
selected={currentItem?.node.id == linkedItem.node.id}
/>
)}
</Box>
))}
</VStack>
{currentItem && (
<HighlightItemsCard item={currentItem} viewer={props.viewer} />
)}
</HStack>
)
}
type HighlightTitleCardProps = {
item: LibraryItem
viewer: UserBasicData
selected: boolean
}
const timeAgo = (date: string | undefined): string => {
if (!date) {
return ''
}
return dayjs(date).fromNow()
}
function HighlightTitleCard(props: HighlightTitleCardProps): JSX.Element {
return (
<HStack
css={{
height: '100%',
width: '100%',
py: '10px',
borderBottom: '1px solid $thBorderColor',
}}
distribution="start"
>
<HStack
css={{
width: '100%',
height: '100%',
py: '15px',
px: '15px',
borderRadius: '10px',
bg: props.selected ? '#3D3D3D' : 'unset',
}}
>
<VStack
css={{
width: '100%',
height: '100%',
borderRadius: '5px',
}}
>
<HStack css={MetaStyle} distribution="start">
<Box>
{timeAgo(props.item.node.savedAt)}
{` `}
{props.item.node.wordsCount ?? 0 > 0
? `${Math.max(
1,
Math.round((props.item.node.wordsCount ?? 0) / 235)
)} min read`
: null}
{props.item.node.readingProgressPercent ?? 0 > 0 ? (
<>
{``}
<SpanBox css={{ color: '#55B938' }}>
{`${Math.round(props.item.node.readingProgressPercent)}%`}
</SpanBox>
</>
) : null}
</Box>
</HStack>
<Box
css={{
mt: '5px',
color: '$thTextContrast2',
fontSize: '16px',
fontWeight: '500',
lineHeight: '1.50',
fontFamily: '$display',
overflow: 'hidden',
textOverflow: 'ellipsis',
wordBreak: 'break-word',
}}
>
{props.item.node.title}
</Box>
</VStack>
<HighlightCountChip
selected={props.selected}
count={props.item.node.highlights?.length ?? 0}
/>
</HStack>
</HStack>
)
}
type HighlightCountChipProps = {
count: number
selected: boolean
}
function HighlightCountChip(props: HighlightCountChipProps): JSX.Element {
return (
<HStack
css={{
gap: '5px',
borderRadius: '12px',
marginLeft: 'auto',
minWidth: '50px',
minHeight: '25px',
width: '47px',
color: props.selected ? '#3D3D3D' : '#898989',
bg: props.selected ? '#D9D9D9' : '#3D3D3D',
fontFamily: '$inter',
fontSize: '14px',
fontWeight: '500',
ml: '30px',
}}
alignment="center"
distribution="center"
>
{props.count}
<HighlighterCircle
size={15}
color={props.selected ? '#3D3D3D' : '#898989'}
/>
</HStack>
)
}
type HighlightItemsCardProps = {
item: LibraryItem
viewer: UserBasicData | undefined
}
function HighlightItemsCard(props: HighlightItemsCardProps): JSX.Element {
return (
<HStack
css={{ height: '100%', flexGrow: '1' }}
distribution="center"
alignment="start"
>
<VStack
css={{
width: '425px',
height: '100%',
// mt: '20px',
//border: '1px solid $thBorderColor',
borderRadius: '6px',
// bg: '#2A2A2A',
}}
alignment="start"
distribution="start"
>
<HStack
css={{
width: '100%',
height: '100%',
pt: '25px',
borderBottom: '1px solid $thBorderColor',
}}
alignment="start"
distribution="start"
>
<StyledText
css={{
fontWeight: '600',
fontSize: '15px',
fontFamily: '$display',
}}
>
HIGHLIGHTS
</StyledText>
</HStack>
<VStack css={{ height: '100%', width: '100%' }} distribution="start">
{(props.item.node.highlights ?? []).map((highlight) => (
<HighlightItemCard key={highlight.id} highlight={highlight} />
))}
</VStack>
</VStack>
</HStack>
)
}
const StyledQuote = styled(Blockquote, {
margin: '0px 0px 0px 0px',
fontSize: '16px',
lineHeight: '1.50',
color: '#D9D9D9',
paddingLeft: '15px',
borderLeft: '2px solid $omnivoreCtaYellow',
})
type HighlightItemCardProps = {
highlight: Highlight
}
function HighlightItemCard(props: HighlightItemCardProps): JSX.Element {
const [isEditing, setIsEditing] = useState(false)
const [hover, setHover] = useState(false)
const lines = useMemo(
() => props.highlight.quote.split('\n'),
[props.highlight.quote]
)
console.log('hover: ', hover)
return (
<HStack
css={{ width: '100%', py: '20px' }}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
<VStack
css={{
gap: '10px',
height: '100%',
cursor: 'pointer',
wordBreak: 'break-word',
overflow: 'clip',
}}
alignment="start"
distribution="start"
>
<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.highlight.labels?.map((label: Label, index: number) => (
<LabelChip
key={index}
text={label.name || ''}
color={label.color}
/>
))}
</Box>
</StyledQuote>
{!isEditing ? (
<StyledText
css={{
borderRadius: '6px',
bg: '#3D3D3D',
p: '10px',
width: '100%',
marginTop: '5px',
color: '$grayText',
}}
onClick={() => setIsEditing(true)}
>
{props.highlight.annotation
? props.highlight.annotation
: 'Add your notes...'}
</StyledText>
) : null}
{isEditing && (
<TextEditArea
setIsEditing={setIsEditing}
highlight={props.highlight}
updateHighlight={() => {}}
/>
)}
</VStack>
<SpanBox
css={{
marginLeft: 'auto',
width: '20px',
visibility: hover ? 'unset' : 'hidden',
'@media (hover: none)': {
visibility: 'unset',
},
}}
>
<DotsThreeVertical size={20} color="#EBEBEB" weight="bold" />
</SpanBox>
</HStack>
)
}

View file

@ -15,12 +15,9 @@ import { LinkedItemCard } from '../../patterns/LibraryCards/LinkedItemCard'
import { useRouter } from 'next/router'
import { Button } from '../../elements/Button'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { LibrarySearchBar } from './LibrarySearchBar'
import { StyledText } from '../../elements/StyledText'
import { AddLinkModal } from './AddLinkModal'
import { styled, theme } from '../../tokens/stitches.config'
import { ListLayoutIcon } from '../../elements/images/ListLayoutIcon'
import { GridLayoutIcon } from '../../elements/images/GridLayoutIcon'
import { libraryListCommands } from '../../../lib/keyboardShortcuts/navigationShortcuts'
import { useKeyboardShortcuts } from '../../../lib/keyboardShortcuts/useKeyboardShortcuts'
import { Toaster } from 'react-hot-toast'
@ -49,22 +46,10 @@ import { uploadFileRequestMutation } from '../../../lib/networking/mutations/upl
import { setLabelsMutation } from '../../../lib/networking/mutations/setLabelsMutation'
import { LibraryHeader } from './LibraryHeader'
import { LibraryFilterMenu } from './LibraryFilterMenu'
import { HighlightItemsLayout } from './HighlightsLayout'
export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT'
const timeZoneHourDiff = -new Date().getTimezoneOffset() / 60
const SAVED_SEARCHES: Record<string, string> = {
Inbox: `in:inbox`,
'Read Later': `in:inbox -label:Newsletter`,
Highlights: `type:highlights`,
Today: `in:inbox saved:${
new Date(new Date().getTime() - 24 * 3600000).toISOString().split('T')[0]
}Z${timeZoneHourDiff.toLocaleString('en-US', {
signDisplay: 'always',
})}..*`,
Newsletters: `in:inbox label:Newsletter`,
}
export type LibraryMode = 'reads' | 'highlights'
const fetchSearchResults = async (query: string, cb: any) => {
if (!query.startsWith('#')) return
@ -634,14 +619,11 @@ const DragnDropStyle = styled('div', {
function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
const { viewerData } = useGetViewerQuery()
const [mode, setMode] = useState<LibraryMode>('reads')
const [layout, setLayout] = usePersistedState<LayoutType>({
key: 'libraryLayout',
initialValue: 'GRID_LAYOUT',
})
const [showRemoveLinkConfirmation, setShowRemoveLinkConfirmation] =
useState(false)
const [showUnsubscribeConfirmation, setShowUnsubscribeConfirmation] =
useState(false)
const updateLayout = useCallback(
async (newLayout: LayoutType) => {
@ -651,6 +633,68 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
[layout, setLayout]
)
const [showFilterMenu, setShowFilterMenu] = useState(false)
return (
<VStack css={{ width: '100%', height: '100%' }}>
<LibraryHeader
layout={layout}
updateLayout={updateLayout}
searchTerm={props.searchTerm}
applySearchQuery={(searchQuery: string) => {
console.log('searching with searchQuery: ', searchQuery)
props.applySearchQuery(searchQuery)
}}
showFilterMenu={showFilterMenu}
setShowFilterMenu={setShowFilterMenu}
/>
<HStack css={{ width: '100%', height: '100%' }}>
<LibraryFilterMenu
setShowAddLinkModal={props.setShowAddLinkModal}
searchTerm={props.searchTerm}
applySearchQuery={(searchQuery: string) => {
console.log('searching with searchQuery: ', searchQuery)
props.applySearchQuery(searchQuery)
}}
showFilterMenu={showFilterMenu}
setShowFilterMenu={setShowFilterMenu}
setMode={setMode}
/>
{mode == 'highlights' && (
<HighlightItemsLayout
gridContainerRef={props.gridContainerRef}
items={props.items}
viewer={viewerData?.me}
/>
)}
{mode == 'reads' && (
<LibraryItemsLayout
viewer={viewerData?.me}
layout={layout}
{...props}
/>
)}
</HStack>
</VStack>
)
}
type LibraryItemsLayoutProps = {
layout: LayoutType
viewer?: UserBasicData
} & HomeFeedContentProps
function LibraryItemsLayout(props: LibraryItemsLayoutProps): JSX.Element {
const [uploadingFiles, setUploadingFiles] = useState([])
const [inDragOperation, setInDragOperation] = useState(false)
const [uploadProgress, setUploadProgress] = useState(0)
const [showRemoveLinkConfirmation, setShowRemoveLinkConfirmation] =
useState(false)
const [showUnsubscribeConfirmation, setShowUnsubscribeConfirmation] =
useState(false)
const [, updateState] = useState({})
const removeItem = () => {
@ -672,10 +716,6 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
setShowUnsubscribeConfirmation(false)
}
const [uploadingFiles, setUploadingFiles] = useState([])
const [inDragOperation, setInDragOperation] = useState(false)
const [uploadProgress, setUploadProgress] = useState(0)
const handleDrop = async (acceptedFiles: any) => {
setInDragOperation(false)
setUploadingFiles(acceptedFiles.map((file: { name: any }) => file.name))
@ -722,247 +762,215 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
props.reloadItems()
}
const [showFilterMenu, setShowFilterMenu] = useState(false)
return (
<VStack css={{ width: '100%', height: '100%' }}>
<LibraryHeader
layout={layout}
updateLayout={updateLayout}
searchTerm={props.searchTerm}
applySearchQuery={(searchQuery: string) => {
console.log('searching with searchQuery: ', searchQuery)
props.applySearchQuery(searchQuery)
<>
<VStack
alignment="start"
distribution="start"
css={{
height: '100%',
}}
showFilterMenu={showFilterMenu}
setShowFilterMenu={setShowFilterMenu}
/>
<HStack css={{ width: '100%', height: '100%' }}>
<LibraryFilterMenu
setShowAddLinkModal={props.setShowAddLinkModal}
searchTerm={props.searchTerm}
applySearchQuery={(searchQuery: string) => {
console.log('searching with searchQuery: ', searchQuery)
props.applySearchQuery(searchQuery)
>
<Toaster />
{props.isValidating && props.items.length == 0 && <TopBarProgress />}
<Dropzone
onDrop={handleDrop}
onDragEnter={() => {
setInDragOperation(true)
}}
showFilterMenu={showFilterMenu}
setShowFilterMenu={setShowFilterMenu}
/>
<VStack
alignment="center"
css={{
width: '100%',
onDragLeave={() => {
setInDragOperation(false)
}}
preventDropOnDocument={true}
noClick={true}
accept={{
'application/pdf': ['.pdf'],
}}
>
<Toaster />
{props.isValidating && props.items.length == 0 && <TopBarProgress />}
<Dropzone
onDrop={handleDrop}
onDragEnter={() => {
setInDragOperation(true)
}}
onDragLeave={() => {
setInDragOperation(false)
}}
preventDropOnDocument={true}
noClick={true}
accept={{
'application/pdf': ['.pdf'],
}}
>
{({
getRootProps,
getInputProps,
acceptedFiles,
fileRejections,
}) => (
<div
{...getRootProps({ className: 'dropzone' })}
style={{ height: '100%' }}
>
{inDragOperation && uploadingFiles.length < 1 && (
<DragnDropContainer>
<DragnDropStyle>
<Box
css={{
color: '$utilityTextDefault',
fontWeight: '800',
fontSize: '$4',
}}
>
Drop PDF document to to upload and add to your library
</Box>
</DragnDropStyle>
</DragnDropContainer>
)}
{uploadingFiles.length > 0 && (
<DragnDropContainer>
<DragnDropStyle>
<Box
css={{
color: '$utilityTextDefault',
fontWeight: '800',
fontSize: '$4',
width: '80%',
}}
>
<Progress.Root
className="ProgressRoot"
value={uploadProgress}
>
<Progress.Indicator
className="ProgressIndicator"
style={{
transform: `translateX(-${
100 - uploadProgress
}%)`,
}}
/>
</Progress.Root>
<StyledText
style="boldText"
css={{
color: theme.colors.omnivoreGray.toString(),
}}
>
Uploading file
</StyledText>
</Box>
</DragnDropStyle>
</DragnDropContainer>
)}
<input {...getInputProps()} />
{!props.isValidating && props.items.length == 0 ? (
<EmptyLibrary
onAddLinkClicked={() => {
props.setShowAddLinkModal(true)
}}
/>
) : (
<LibraryItems
items={props.items}
layout={layout}
viewer={viewerData?.me}
gridContainerRef={props.gridContainerRef}
setShowEditTitleModal={props.setShowEditTitleModal}
setLinkToEdit={props.setLinkToEdit}
setShowUnsubscribeConfirmation={
setShowUnsubscribeConfirmation
}
setLinkToRemove={props.setLinkToRemove}
setLinkToUnsubscribe={props.setLinkToUnsubscribe}
setShowRemoveLinkConfirmation={
setShowRemoveLinkConfirmation
}
actionHandler={props.actionHandler}
/>
)}
<HStack
distribution="center"
css={{ width: '100%', mt: '$2', mb: '$4' }}
>
{props.hasMore ? (
<Button
style="ctaGray"
{({ getRootProps, getInputProps, acceptedFiles, fileRejections }) => (
<div
{...getRootProps({ className: 'dropzone' })}
style={{ height: '100%', width: '100%' }}
>
{inDragOperation && uploadingFiles.length < 1 && (
<DragnDropContainer>
<DragnDropStyle>
<Box
css={{
cursor: props.isValidating ? 'not-allowed' : 'pointer',
color: '$utilityTextDefault',
fontWeight: '800',
fontSize: '$4',
}}
onClick={props.loadMore}
disabled={props.isValidating}
>
{props.isValidating ? 'Loading' : 'Load More'}
</Button>
) : (
<StyledText style="caption"></StyledText>
)}
</HStack>
</div>
)}
</Dropzone>
</VStack>
{props.showAddLinkModal && (
<AddLinkModal onOpenChange={() => props.setShowAddLinkModal(false)} />
)}
{props.showEditTitleModal && (
<EditLibraryItemModal
updateItem={(item: LibraryItem) =>
props.actionHandler('update-item', item)
}
onOpenChange={() => props.setShowEditTitleModal(false)}
item={props.linkToEdit as LibraryItem}
/>
)}
{showRemoveLinkConfirmation && (
<ConfirmationModal
richMessage={
<VStack alignment="center" distribution="center">
<StyledText style="modalTitle" css={{ margin: '0px 8px' }}>
Are you sure you want to delete this item? All associated
notes and highlights will be deleted.
</StyledText>
{props.linkToRemove?.node && viewerData?.me && (
<Box
Drop PDF document to to upload and add to your library
</Box>
</DragnDropStyle>
</DragnDropContainer>
)}
{uploadingFiles.length > 0 && (
<DragnDropContainer>
<DragnDropStyle>
<Box
css={{
color: '$utilityTextDefault',
fontWeight: '800',
fontSize: '$4',
width: '80%',
}}
>
<Progress.Root
className="ProgressRoot"
value={uploadProgress}
>
<Progress.Indicator
className="ProgressIndicator"
style={{
transform: `translateX(-${100 - uploadProgress}%)`,
}}
/>
</Progress.Root>
<StyledText
style="boldText"
css={{
color: theme.colors.omnivoreGray.toString(),
}}
>
Uploading file
</StyledText>
</Box>
</DragnDropStyle>
</DragnDropContainer>
)}
<input {...getInputProps()} />
{!props.isValidating && props.items.length == 0 ? (
<EmptyLibrary
onAddLinkClicked={() => {
props.setShowAddLinkModal(true)
}}
/>
) : (
<LibraryItems
items={props.items}
layout={props.layout}
viewer={props.viewer}
gridContainerRef={props.gridContainerRef}
setShowEditTitleModal={props.setShowEditTitleModal}
setLinkToEdit={props.setLinkToEdit}
setShowUnsubscribeConfirmation={
setShowUnsubscribeConfirmation
}
setLinkToRemove={props.setLinkToRemove}
setLinkToUnsubscribe={props.setLinkToUnsubscribe}
setShowRemoveLinkConfirmation={setShowRemoveLinkConfirmation}
actionHandler={props.actionHandler}
/>
)}
<HStack
distribution="center"
css={{ width: '100%', mt: '$2', mb: '$4' }}
>
{props.hasMore ? (
<Button
style="ctaGray"
css={{
transform: 'scale(0.6)',
opacity: 0.8,
pointerEvents: 'none',
filter: 'grayscale(1)',
cursor: props.isValidating ? 'not-allowed' : 'pointer',
}}
onClick={props.loadMore}
disabled={props.isValidating}
>
<LinkedItemCard
item={props.linkToRemove?.node}
viewer={viewerData.me}
layout="GRID_LAYOUT"
// eslint-disable-next-line @typescript-eslint/no-empty-function
handleAction={() => {}}
/>
</Box>
{props.isValidating ? 'Loading' : 'Load More'}
</Button>
) : (
<StyledText style="caption"></StyledText>
)}
</VStack>
</HStack>
</div>
)}
</Dropzone>
</VStack>
{props.showAddLinkModal && (
<AddLinkModal onOpenChange={() => props.setShowAddLinkModal(false)} />
)}
{props.showEditTitleModal && (
<EditLibraryItemModal
updateItem={(item: LibraryItem) =>
props.actionHandler('update-item', item)
}
onOpenChange={() => props.setShowEditTitleModal(false)}
item={props.linkToEdit as LibraryItem}
/>
)}
{showRemoveLinkConfirmation && (
<ConfirmationModal
richMessage={
<VStack alignment="center" distribution="center">
<StyledText style="modalTitle" css={{ margin: '0px 8px' }}>
Are you sure you want to delete this item? All associated notes
and highlights will be deleted.
</StyledText>
{props.linkToRemove?.node && props.viewer && (
<Box
css={{
transform: 'scale(0.6)',
opacity: 0.8,
pointerEvents: 'none',
filter: 'grayscale(1)',
}}
>
<LinkedItemCard
item={props.linkToRemove?.node}
viewer={props.viewer}
layout="GRID_LAYOUT"
// eslint-disable-next-line @typescript-eslint/no-empty-function
handleAction={() => {}}
/>
</Box>
)}
</VStack>
}
onAccept={removeItem}
acceptButtonLabel="Delete Item"
onOpenChange={() => setShowRemoveLinkConfirmation(false)}
/>
)}
{showUnsubscribeConfirmation && (
<ConfirmationModal
message={'Are you sure you want to unsubscribe?'}
onAccept={unsubscribe}
onOpenChange={() => setShowUnsubscribeConfirmation(false)}
/>
)}
{props.labelsTarget?.node.id && (
<SetLabelsModal
provider={props.labelsTarget.node}
onLabelsUpdated={(labels: Label[]) => {
if (props.labelsTarget) {
props.labelsTarget.node.labels = labels
updateState({})
}
onAccept={removeItem}
acceptButtonLabel="Delete Item"
onOpenChange={() => setShowRemoveLinkConfirmation(false)}
/>
)}
{showUnsubscribeConfirmation && (
<ConfirmationModal
message={'Are you sure you want to unsubscribe?'}
onAccept={unsubscribe}
onOpenChange={() => setShowUnsubscribeConfirmation(false)}
/>
)}
{props.labelsTarget?.node.id && (
<SetLabelsModal
provider={props.labelsTarget.node}
onLabelsUpdated={(labels: Label[]) => {
if (props.labelsTarget) {
props.labelsTarget.node.labels = labels
updateState({})
}
}}
save={(labels: Label[]) => {
if (props.labelsTarget?.node.id) {
return setLabelsMutation(
props.labelsTarget.node.id,
labels.map((label) => label.id)
)
}
return Promise.resolve(undefined)
}}
onOpenChange={() => {
if (props.labelsTarget) {
const activate = props.labelsTarget
props.setActiveItem(activate)
props.setLabelsTarget(undefined)
}
}}
/>
)}
</HStack>
</VStack>
}}
save={(labels: Label[]) => {
if (props.labelsTarget?.node.id) {
return setLabelsMutation(
props.labelsTarget.node.id,
labels.map((label) => label.id)
)
}
return Promise.resolve(undefined)
}}
onOpenChange={() => {
if (props.labelsTarget) {
const activate = props.labelsTarget
props.setActiveItem(activate)
props.setLabelsTarget(undefined)
}
}}
/>
)}
</>
)
}

View file

@ -11,6 +11,7 @@ import { theme } from '../../tokens/stitches.config'
import { currentThemeName } from '../../../lib/themeUpdater'
import { MOBILE_HEADER_HEIGHT } from './HeaderSpacer'
import { useRegisterActions } from 'kbar'
import { LibraryMode } from './HomeFeedContainer'
export const LIBRARY_LEFT_MENU_WIDTH = '300px'
@ -22,6 +23,8 @@ type LibraryFilterMenuProps = {
showFilterMenu: boolean
setShowFilterMenu: (show: boolean) => void
setMode: (mode: LibraryMode) => void
}
export function LibraryFilterMenu(props: LibraryFilterMenuProps): JSX.Element {
@ -86,6 +89,7 @@ function SavedSearches(props: LibraryFilterMenuProps): JSX.Element {
{
name: 'Highlights',
term: 'has:highlights',
mode: 'highlights',
},
{
name: 'Unlabeled',
@ -111,6 +115,7 @@ function SavedSearches(props: LibraryFilterMenuProps): JSX.Element {
section: 'Saved Searches',
keywords: '?' + item.name,
perform: () => {
props.setMode(item.mode ?? 'reads')
props.applySearchQuery(item.term)
},
}
@ -125,6 +130,7 @@ function SavedSearches(props: LibraryFilterMenuProps): JSX.Element {
key={item.name}
text={item.name}
filterTerm={item.term}
mode={item.mode}
{...props}
/>
))}
@ -285,8 +291,11 @@ type FilterButtonProps = {
filterTerm: string
searchTerm: string | undefined
mode?: LibraryMode
applySearchQuery: (searchTerm: string) => void
setMode: (mode: LibraryMode) => void
setShowFilterMenu: (show: boolean) => void
}
@ -330,6 +339,7 @@ function FilterButton(props: FilterButtonProps): JSX.Element {
},
}}
onClick={(e) => {
props.setMode(props.mode ?? 'reads')
props.applySearchQuery(props.filterTerm)
props.setShowFilterMenu(false)
e.preventDefault()

View file

@ -5218,6 +5218,20 @@
"@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "1.0.0"
"@radix-ui/react-switch@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-switch/-/react-switch-1.0.1.tgz#56665fa951a4190313be21fab70a90041bdbd191"
integrity sha512-tTxGluMtwrc5ffgAiOSMrYIx0r3vSTcgM4Vl8rqfpXcHt6ryB9B0OlFKUOiDpKASXlhvzfHf4Y0AYKJdpzjL8w==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "1.0.0"
"@radix-ui/react-compose-refs" "1.0.0"
"@radix-ui/react-context" "1.0.0"
"@radix-ui/react-primitive" "1.0.1"
"@radix-ui/react-use-controllable-state" "1.0.0"
"@radix-ui/react-use-previous" "1.0.0"
"@radix-ui/react-use-size" "1.0.0"
"@radix-ui/react-tooltip@^0.1.7":
version "0.1.7"
resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-0.1.7.tgz#6f8c00d6e489565d14abf209ce0fb8853c8c8ee3"