Improve multiselect
This commit is contained in:
@ -11,9 +11,8 @@ export function ProgressBar(props: ProgressBarProps): JSX.Element {
|
||||
return (
|
||||
<Box
|
||||
css={{
|
||||
height: '4px',
|
||||
height: '5px',
|
||||
width: '100%',
|
||||
borderRadius: '$1',
|
||||
overflow: 'hidden',
|
||||
backgroundColor: props.backgroundColor,
|
||||
}}
|
||||
@ -23,7 +22,6 @@ export function ProgressBar(props: ProgressBarProps): JSX.Element {
|
||||
height: '100%',
|
||||
width: `${props.fillPercentage}%`,
|
||||
backgroundColor: props.fillColor,
|
||||
borderRadius: props.borderRadius,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { LayoutType } from '../../templates/homeFeed/HomeFeedContainer'
|
||||
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
import type { LibraryItemNode } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { MultiSelectMode } from '../../templates/homeFeed/LibraryHeader'
|
||||
|
||||
export type LinkedItemCardAction =
|
||||
| 'showDetail'
|
||||
@ -23,9 +24,10 @@ export type LinkedItemCardProps = {
|
||||
|
||||
handleAction: (action: LinkedItemCardAction) => void
|
||||
|
||||
inMultiSelect: boolean
|
||||
isChecked: boolean
|
||||
setIsChecked: (itemId: string, set: boolean) => void
|
||||
|
||||
multiSelectMode: MultiSelectMode
|
||||
|
||||
isHovered?: boolean
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { useMemo } from 'react'
|
||||
import { ChangeEvent, useMemo } from 'react'
|
||||
import { LibraryItemNode } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { Box, SpanBox } from '../../elements/LayoutPrimitives'
|
||||
import { Box, SpanBox, VStack } from '../../elements/LayoutPrimitives'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
@ -25,7 +25,7 @@ export const MenuStyle = {
|
||||
export const MetaStyle = {
|
||||
width: '100%',
|
||||
color: '$thTextSubtle3',
|
||||
fontSize: '13px',
|
||||
fontSize: '12px',
|
||||
fontWeight: '400',
|
||||
fontFamily: '$display',
|
||||
}
|
||||
@ -34,7 +34,7 @@ export const TitleStyle = {
|
||||
color: '$thTextContrast2',
|
||||
fontSize: '16px',
|
||||
fontWeight: '700',
|
||||
lineHeight: '1.25',
|
||||
lineHeight: '1',
|
||||
fontFamily: '$display',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
@ -67,11 +67,11 @@ export const AuthorInfoStyle = {
|
||||
whiteSpace: 'nowrap',
|
||||
maxWidth: '240px',
|
||||
overflow: 'hidden',
|
||||
height: '21px',
|
||||
color: '$thTextSubtle3',
|
||||
fontSize: '12px',
|
||||
fontWeight: '400',
|
||||
fontFamily: '$display',
|
||||
lineHeight: '1',
|
||||
}
|
||||
|
||||
export const timeAgo = (date: string | undefined): string => {
|
||||
@ -137,14 +137,6 @@ export function LibraryItemMetadata(
|
||||
{highlightCount > 0
|
||||
? ` • ${highlightCount} highlight${highlightCount > 1 ? 's' : ''}`
|
||||
: null}
|
||||
{(props.showProgress && props.item.readingProgressPercent) ?? 0 > 0 ? (
|
||||
<>
|
||||
{` | `}
|
||||
<SpanBox css={{ color: '#55B938' }}>
|
||||
{`${Math.round(props.item.readingProgressPercent)}%`}
|
||||
</SpanBox>
|
||||
</>
|
||||
) : null}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@ -156,10 +148,19 @@ type CardCheckBoxProps = {
|
||||
|
||||
export function CardCheckbox(props: CardCheckBoxProps): JSX.Element {
|
||||
return (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={props.isChecked}
|
||||
onChange={props.handleChanged}
|
||||
></input>
|
||||
<form
|
||||
// This prevents us from propogating up the the <a element on cards
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={props.isChecked}
|
||||
onChange={(event) => {
|
||||
props.handleChanged()
|
||||
}}
|
||||
></input>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import type { LinkedItemCardProps } from './CardTypes'
|
||||
import { CoverImage } from '../../elements/CoverImage'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { ChangeEvent, useCallback, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import {
|
||||
AuthorInfoStyle,
|
||||
@ -29,6 +29,8 @@ import {
|
||||
import { CardMenu } from '../CardMenu'
|
||||
import { DotsThree } from 'phosphor-react'
|
||||
import { isTouchScreenDevice } from '../../../lib/deviceType'
|
||||
import { ProgressBarOverlay } from './LibraryListCard'
|
||||
import { FallbackImage } from './FallbackImage'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
@ -115,38 +117,32 @@ export function LibraryGridCard(props: LinkedItemCardProps): JSX.Element {
|
||||
setIsHovered(false)
|
||||
}}
|
||||
>
|
||||
{props.inMultiSelect ? (
|
||||
<LibraryGridCardContent {...props} isHovered={isHovered} />
|
||||
) : (
|
||||
<>
|
||||
{!isTouchScreenDevice() && (
|
||||
<Box
|
||||
ref={refs.setFloating}
|
||||
style={{ ...floatingStyles, zIndex: 10 }}
|
||||
{...getFloatingProps()}
|
||||
>
|
||||
<LibraryHoverActions
|
||||
item={props.item}
|
||||
viewer={props.viewer}
|
||||
handleAction={props.handleAction}
|
||||
isHovered={isHovered ?? false}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Link
|
||||
href={`${props.viewer.profile.username}/${props.item.slug}`}
|
||||
passHref
|
||||
>
|
||||
<a
|
||||
href={`${props.viewer.profile.username}/${props.item.slug}`}
|
||||
style={{ textDecoration: 'unset', width: '100%', height: '100%' }}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<LibraryGridCardContent {...props} isHovered={isHovered} />
|
||||
</a>
|
||||
</Link>
|
||||
</>
|
||||
{!isTouchScreenDevice() && (
|
||||
<Box
|
||||
ref={refs.setFloating}
|
||||
style={{ ...floatingStyles, zIndex: 10 }}
|
||||
{...getFloatingProps()}
|
||||
>
|
||||
<LibraryHoverActions
|
||||
item={props.item}
|
||||
viewer={props.viewer}
|
||||
handleAction={props.handleAction}
|
||||
isHovered={isHovered ?? false}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Link
|
||||
href={`${props.viewer.profile.username}/${props.item.slug}`}
|
||||
passHref
|
||||
>
|
||||
<a
|
||||
href={`${props.viewer.profile.username}/${props.item.slug}`}
|
||||
style={{ textDecoration: 'unset', width: '100%', height: '100%' }}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<LibraryGridCardContent {...props} isHovered={isHovered} />
|
||||
</a>
|
||||
</Link>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
@ -189,25 +185,43 @@ const Fallback = (props: FallbackProps): JSX.Element => {
|
||||
type GridImageProps = {
|
||||
src?: string
|
||||
title?: string
|
||||
readingProgress?: number
|
||||
}
|
||||
|
||||
const GridImage = (props: GridImageProps): JSX.Element => {
|
||||
const [displayFallback, setDisplayFallback] = useState(props.src == undefined)
|
||||
|
||||
return displayFallback ? (
|
||||
<Fallback title={props.title ?? 'Omnivore Fallback'} />
|
||||
) : (
|
||||
<CoverImage
|
||||
src={props.src}
|
||||
width="100%"
|
||||
height={100}
|
||||
css={{
|
||||
bg: '$thBackground',
|
||||
}}
|
||||
onError={(e) => {
|
||||
setDisplayFallback(true)
|
||||
}}
|
||||
/>
|
||||
return (
|
||||
<>
|
||||
{(props.readingProgress ?? 0) > 0 && (
|
||||
<ProgressBarOverlay
|
||||
width="100%"
|
||||
top={95}
|
||||
value={props.readingProgress ?? 0}
|
||||
bottomRadius={'0px'}
|
||||
/>
|
||||
)}
|
||||
{displayFallback ? (
|
||||
<FallbackImage
|
||||
title={props.title ?? 'Omnivore Fallback'}
|
||||
width="100%"
|
||||
height="100px"
|
||||
fontSize="128px"
|
||||
/>
|
||||
) : (
|
||||
<CoverImage
|
||||
src={props.src}
|
||||
width="100%"
|
||||
height={100}
|
||||
css={{
|
||||
bg: '$thBackground',
|
||||
}}
|
||||
onError={(e) => {
|
||||
setDisplayFallback(true)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -217,45 +231,55 @@ const LibraryGridCardContent = (props: LinkedItemCardProps): JSX.Element => {
|
||||
const originText = siteName(props.item.originalArticleUrl, props.item.url)
|
||||
|
||||
const handleCheckChanged = useCallback(() => {
|
||||
setIsChecked(item.id, !isChecked)
|
||||
}, [setIsChecked, isChecked])
|
||||
const newValue = !isChecked
|
||||
setIsChecked(item.id, newValue)
|
||||
}, [setIsChecked, isChecked, props])
|
||||
|
||||
return (
|
||||
<VStack css={{ p: '0px', m: '0px', width: '100%' }}>
|
||||
<Box css={{ position: 'relative', width: '100%' }}>
|
||||
<GridImage src={props.item.image} title={props.item.title} />
|
||||
{props.inMultiSelect ? (
|
||||
<SpanBox css={{ position: 'absolute', top: 0, left: 0, m: '5px' }}>
|
||||
<CardCheckbox
|
||||
isChecked={props.isChecked}
|
||||
handleChanged={handleCheckChanged}
|
||||
/>
|
||||
</SpanBox>
|
||||
) : (
|
||||
<Box
|
||||
css={{
|
||||
...MenuStyle,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
m: '5px',
|
||||
visibility: menuOpen ? 'visible' : 'hidden',
|
||||
'@media (hover: none)': {
|
||||
visibility: 'unset',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CardMenu
|
||||
item={props.item}
|
||||
viewer={props.viewer}
|
||||
onOpenChange={(open) => setMenuOpen(open)}
|
||||
actionHandler={props.handleAction}
|
||||
triggerElement={
|
||||
<DotsThree size={25} weight="bold" color="#ADADAD" />
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<GridImage
|
||||
src={props.item.image}
|
||||
title={props.item.title}
|
||||
readingProgress={item.readingProgressPercent}
|
||||
/>
|
||||
<SpanBox
|
||||
css={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
m: '10px',
|
||||
lineHeight: '1',
|
||||
}}
|
||||
>
|
||||
<CardCheckbox
|
||||
isChecked={isChecked}
|
||||
handleChanged={handleCheckChanged}
|
||||
/>
|
||||
</SpanBox>
|
||||
<Box
|
||||
css={{
|
||||
...MenuStyle,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
m: '5px',
|
||||
visibility: menuOpen ? 'visible' : 'hidden',
|
||||
'@media (hover: none)': {
|
||||
visibility: 'unset',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CardMenu
|
||||
item={props.item}
|
||||
viewer={props.viewer}
|
||||
onOpenChange={(open) => setMenuOpen(open)}
|
||||
actionHandler={props.handleAction}
|
||||
triggerElement={
|
||||
<DotsThree size={25} weight="bold" color="#ADADAD" />
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<HStack
|
||||
@ -299,7 +323,7 @@ const LibraryGridCardContent = (props: LinkedItemCardProps): JSX.Element => {
|
||||
<HStack
|
||||
distribution="start"
|
||||
alignment="start"
|
||||
css={{ width: '100%', minHeight: '50px' }}
|
||||
css={{ width: '100%', minHeight: '50px', pb: '10px' }}
|
||||
>
|
||||
<HStack
|
||||
css={{
|
||||
|
||||
@ -38,7 +38,6 @@ export function LibraryHighlightGridCard(
|
||||
showErrorToast('Error navigating to highlight')
|
||||
return
|
||||
}
|
||||
console.log('pushing user: ', props.viewer, 'slug: ', props.item.slug)
|
||||
router.push(
|
||||
{
|
||||
pathname: '/[username]/[slug]',
|
||||
|
||||
@ -32,8 +32,6 @@ type LibraryHoverActionsProps = {
|
||||
export const LibraryHoverActions = (props: LibraryHoverActionsProps) => {
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
|
||||
console.log(' props.isHovered || menuOpen', props.isHovered, menuOpen)
|
||||
|
||||
return (
|
||||
<Box
|
||||
css={{
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
siteName,
|
||||
TitleStyle,
|
||||
MenuStyle,
|
||||
Fallback,
|
||||
} from './LibraryCardStyles'
|
||||
import { sortedLabels } from '../../../lib/labelsSort'
|
||||
import { LIBRARY_LEFT_MENU_WIDTH } from '../../templates/homeFeed/LibraryFilterMenu'
|
||||
@ -26,6 +27,10 @@ import {
|
||||
import { CardMenu } from '../CardMenu'
|
||||
import { DotsThree } from 'phosphor-react'
|
||||
import { isTouchScreenDevice } from '../../../lib/deviceType'
|
||||
import { CoverImage } from '../../elements/CoverImage'
|
||||
import { ProgressBar } from '../../elements/ProgressBar'
|
||||
import { theme } from '../../tokens/stitches.config'
|
||||
import { FallbackImage } from './FallbackImage'
|
||||
|
||||
export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
@ -55,6 +60,7 @@ export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
|
||||
{...getReferenceProps()}
|
||||
css={{
|
||||
px: '20px',
|
||||
pl: '10px',
|
||||
pt: '20px',
|
||||
pb: '20px',
|
||||
height: '100%',
|
||||
@ -92,42 +98,112 @@ export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
|
||||
setIsHovered(false)
|
||||
}}
|
||||
>
|
||||
{props.inMultiSelect ? (
|
||||
<LibraryListCardContent {...props} isHovered={isHovered} />
|
||||
) : (
|
||||
<>
|
||||
{!isTouchScreenDevice() && (
|
||||
<Box
|
||||
ref={refs.setFloating}
|
||||
style={floatingStyles}
|
||||
{...getFloatingProps()}
|
||||
>
|
||||
<LibraryHoverActions
|
||||
item={props.item}
|
||||
viewer={props.viewer}
|
||||
handleAction={props.handleAction}
|
||||
isHovered={isHovered ?? false}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Link
|
||||
href={`${props.viewer.profile.username}/${props.item.slug}`}
|
||||
passHref
|
||||
>
|
||||
<a
|
||||
href={`${props.viewer.profile.username}/${props.item.slug}`}
|
||||
style={{ textDecoration: 'unset', width: '100%', height: '100%' }}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<LibraryListCardContent {...props} isHovered={isHovered} />
|
||||
</a>
|
||||
</Link>
|
||||
</>
|
||||
{!isTouchScreenDevice() && (
|
||||
<Box
|
||||
ref={refs.setFloating}
|
||||
style={floatingStyles}
|
||||
{...getFloatingProps()}
|
||||
>
|
||||
<LibraryHoverActions
|
||||
item={props.item}
|
||||
viewer={props.viewer}
|
||||
handleAction={props.handleAction}
|
||||
isHovered={isHovered ?? false}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Link
|
||||
href={`${props.viewer.profile.username}/${props.item.slug}`}
|
||||
passHref
|
||||
>
|
||||
<a
|
||||
href={`${props.viewer.profile.username}/${props.item.slug}`}
|
||||
style={{ textDecoration: 'unset', width: '100%', height: '100%' }}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<LibraryListCardContent {...props} isHovered={isHovered} />
|
||||
</a>
|
||||
</Link>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
||||
type ProgressBarOverlayProps = {
|
||||
top: number
|
||||
width: string
|
||||
value: number
|
||||
bottomRadius: string
|
||||
}
|
||||
|
||||
export const ProgressBarOverlay = (
|
||||
props: ProgressBarOverlayProps
|
||||
): JSX.Element => {
|
||||
return (
|
||||
<Box
|
||||
css={{
|
||||
position: 'absolute',
|
||||
width: props.width,
|
||||
top: props.top,
|
||||
borderBottomLeftRadius: props.bottomRadius,
|
||||
borderBottomRightRadius: props.bottomRadius,
|
||||
overflow: 'clip',
|
||||
zIndex: 20,
|
||||
}}
|
||||
>
|
||||
<ProgressBar
|
||||
fillPercentage={props.value}
|
||||
fillColor={theme.colors.thProgressFg.toString()}
|
||||
backgroundColor="rgba(217, 217, 217, 0.65)"
|
||||
borderRadius={'2px'}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
type ListImageProps = {
|
||||
src?: string
|
||||
title?: string
|
||||
readingProgress?: number
|
||||
}
|
||||
|
||||
const ListImage = (props: ListImageProps): JSX.Element => {
|
||||
const [displayFallback, setDisplayFallback] = useState(props.src == undefined)
|
||||
|
||||
return (
|
||||
<>
|
||||
{(props.readingProgress ?? 0) > 0 && (
|
||||
<ProgressBarOverlay
|
||||
width="55px"
|
||||
top={50}
|
||||
bottomRadius="4px"
|
||||
value={props.readingProgress ?? 0}
|
||||
/>
|
||||
)}
|
||||
{displayFallback ? (
|
||||
<FallbackImage
|
||||
title={props.title ?? 'Omnivore Fallback'}
|
||||
width="55px"
|
||||
height="55px"
|
||||
fontSize="36pt"
|
||||
/>
|
||||
) : (
|
||||
<CoverImage
|
||||
src={props.src}
|
||||
width={55}
|
||||
height={55}
|
||||
css={{
|
||||
bg: '$thBackground',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
onError={() => {
|
||||
setDisplayFallback(true)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function LibraryListCardContent(
|
||||
props: LinkedItemCardProps
|
||||
): JSX.Element {
|
||||
@ -140,57 +216,96 @@ export function LibraryListCardContent(
|
||||
}, [isChecked, setIsChecked, item])
|
||||
|
||||
return (
|
||||
<>
|
||||
<HStack css={MetaStyle} distribution="start">
|
||||
<LibraryItemMetadata item={props.item} showProgress={true} />
|
||||
{props.inMultiSelect ? (
|
||||
<SpanBox css={{ marginLeft: 'auto' }}>
|
||||
<CardCheckbox
|
||||
isChecked={props.isChecked}
|
||||
handleChanged={handleCheckChanged}
|
||||
/>
|
||||
</SpanBox>
|
||||
) : (
|
||||
<Box
|
||||
css={{
|
||||
...MenuStyle,
|
||||
visibility: menuOpen ? 'visible' : 'hidden',
|
||||
'@media (hover: none)': {
|
||||
visibility: 'unset',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CardMenu
|
||||
item={props.item}
|
||||
viewer={props.viewer}
|
||||
onOpenChange={(open) => setMenuOpen(open)}
|
||||
actionHandler={props.handleAction}
|
||||
triggerElement={
|
||||
<DotsThree size={25} weight="bold" color="#ADADAD" />
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</HStack>
|
||||
<HStack css={{ gap: '15px', width: '100%' }}>
|
||||
<SpanBox
|
||||
css={{
|
||||
display: 'flex',
|
||||
m: '0px',
|
||||
mt: '0px',
|
||||
p: '0px',
|
||||
ml: '4px',
|
||||
lineHeight: '1',
|
||||
'> input': {
|
||||
p: '0px',
|
||||
m: '0px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CardCheckbox
|
||||
isChecked={props.isChecked}
|
||||
handleChanged={handleCheckChanged}
|
||||
/>
|
||||
</SpanBox>
|
||||
<Box css={{ position: 'relative', width: '55px' }}>
|
||||
<ListImage
|
||||
src={props.item.image}
|
||||
title={props.item.title}
|
||||
readingProgress={item.readingProgressPercent}
|
||||
/>
|
||||
|
||||
<Box
|
||||
css={{
|
||||
...MenuStyle,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
m: '5px',
|
||||
visibility: menuOpen ? 'visible' : 'hidden',
|
||||
'@media (hover: none)': {
|
||||
visibility: 'unset',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CardMenu
|
||||
item={props.item}
|
||||
viewer={props.viewer}
|
||||
onOpenChange={(open) => setMenuOpen(open)}
|
||||
actionHandler={props.handleAction}
|
||||
triggerElement={
|
||||
<DotsThree size={25} weight="bold" color="#ADADAD" />
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<VStack
|
||||
alignment="start"
|
||||
distribution="start"
|
||||
css={{ height: '100%', width: '100%' }}
|
||||
css={{ height: '100%', width: '100%', lineHeight: 1, gap: '5px' }}
|
||||
>
|
||||
<Box css={{ ...TitleStyle, fontSize: '18px', width: '80%' }}>
|
||||
{props.item.title}
|
||||
</Box>
|
||||
<SpanBox
|
||||
<VStack
|
||||
alignment="start"
|
||||
distribution="center"
|
||||
css={{
|
||||
mt: '5px',
|
||||
...AuthorInfoStyle,
|
||||
maxWidth: '90%',
|
||||
height: '55px',
|
||||
width: '100%',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
{props.item.author}
|
||||
{props.item.author && originText && ' | '}
|
||||
<SpanBox css={{ textDecoration: 'underline' }}>{originText}</SpanBox>
|
||||
</SpanBox>
|
||||
<HStack
|
||||
css={{
|
||||
...MetaStyle,
|
||||
color: '$grayText',
|
||||
}}
|
||||
distribution="start"
|
||||
>
|
||||
<LibraryItemMetadata item={props.item} showProgress={true} />
|
||||
</HStack>
|
||||
|
||||
<Box css={{ ...TitleStyle, width: '80%' }}>{props.item.title}</Box>
|
||||
<SpanBox
|
||||
css={{
|
||||
...AuthorInfoStyle,
|
||||
maxWidth: '90%',
|
||||
minHeight: '12px',
|
||||
}}
|
||||
>
|
||||
{props.item.author}
|
||||
{props.item.author && originText && ' | '}
|
||||
<SpanBox css={{ textDecoration: 'underline' }}>
|
||||
{originText}
|
||||
</SpanBox>
|
||||
</SpanBox>
|
||||
</VStack>
|
||||
|
||||
<HStack
|
||||
distribution="start"
|
||||
@ -209,6 +324,6 @@ export function LibraryListCardContent(
|
||||
</HStack>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</>
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import { logoutMutation } from '../../lib/networking/mutations/logoutMutation'
|
||||
import { setupAnalytics } from '../../lib/analytics'
|
||||
import { primaryCommands } from '../../lib/keyboardShortcuts/navigationShortcuts'
|
||||
import { applyStoredTheme } from '../../lib/themeUpdater'
|
||||
import { theme } from '../tokens/stitches.config'
|
||||
|
||||
type PrimaryLayoutProps = {
|
||||
children: ReactNode
|
||||
|
||||
@ -130,7 +130,6 @@ export function UploadModal(props: UploadModalProps): JSX.Element {
|
||||
|
||||
Papa.parse(file.file, {
|
||||
step: function (row, parser) {
|
||||
console.log('row: ', row)
|
||||
if (Array.isArray(row.data)) {
|
||||
try {
|
||||
if (row.data[0].trim().length < 1) {
|
||||
@ -211,7 +210,6 @@ export function UploadModal(props: UploadModalProps): JSX.Element {
|
||||
;(async () => {
|
||||
for (const file of addedFiles) {
|
||||
try {
|
||||
console.log('using content type: ', file.file.type)
|
||||
const uploadInfo = await uploadSignedUrlForFile(file)
|
||||
if (!uploadInfo.uploadSignedUrl) {
|
||||
showErrorToast('No upload URL available')
|
||||
|
||||
@ -192,7 +192,6 @@ export function Article(props: ArticleProps): JSX.Element {
|
||||
const img = element as HTMLImageElement
|
||||
const width = Number(img.getAttribute('data-omnivore-width'))
|
||||
const height = Number(img.getAttribute('data-omnivore-height'))
|
||||
console.log('width and height: ', width, height)
|
||||
|
||||
if (!isNaN(width) && !isNaN(height) && width < 100 && height < 100) {
|
||||
img.style.setProperty('width', `${width}px`)
|
||||
|
||||
@ -297,7 +297,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
||||
const { target, pageX, pageY } = event
|
||||
|
||||
if (!target || (target as Node)?.nodeType !== Node.ELEMENT_NODE) {
|
||||
console.log(' -- returning early from page tap')
|
||||
return
|
||||
}
|
||||
|
||||
@ -375,7 +374,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
||||
highlightIdAttribute
|
||||
)
|
||||
const highlight = highlights.find(($0) => $0.id === id)
|
||||
console.log('double tapped highlight: ', highlight)
|
||||
setFocusedHighlight(highlight)
|
||||
|
||||
openNoteModal({
|
||||
@ -387,8 +385,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
||||
highlightNoteIdAttribute
|
||||
)
|
||||
const highlight = highlights.find(($0) => $0.id === id)
|
||||
console.log('double tapped highlight with note: ', highlight)
|
||||
|
||||
setFocusedHighlight(highlight)
|
||||
|
||||
openNoteModal({
|
||||
|
||||
@ -91,7 +91,6 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
|
||||
)
|
||||
|
||||
const createNote = useCallback((text: string) => {
|
||||
console.log('creating note: ', newNoteId, noteState.current.isCreating)
|
||||
noteState.current.isCreating = true
|
||||
noteState.current.createStarted = new Date()
|
||||
;(async () => {
|
||||
|
||||
@ -84,11 +84,13 @@ export function HomeFeedContainer(): JSX.Element {
|
||||
|
||||
const gridContainerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const [labelsTarget, setLabelsTarget] =
|
||||
useState<LibraryItem | undefined>(undefined)
|
||||
const [labelsTarget, setLabelsTarget] = useState<LibraryItem | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
const [notebookTarget, setNotebookTarget] =
|
||||
useState<LibraryItem | undefined>(undefined)
|
||||
const [notebookTarget, setNotebookTarget] = useState<LibraryItem | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
const [showAddLinkModal, setShowAddLinkModal] = useState(false)
|
||||
const [showEditTitleModal, setShowEditTitleModal] = useState(false)
|
||||
@ -372,15 +374,12 @@ export function HomeFeedContainer(): JSX.Element {
|
||||
const [multiSelectMode, setMultiSelectMode] = useState<MultiSelectMode>('off')
|
||||
|
||||
const selectActiveArticle = useCallback(() => {
|
||||
console.log('selecting article: ', activeItem)
|
||||
if (activeItem) {
|
||||
if (multiSelectMode === 'off') {
|
||||
console.log('setting ')
|
||||
setMultiSelectMode('some')
|
||||
}
|
||||
const itemId = activeItem.node.id
|
||||
const isChecked = itemIsChecked(itemId)
|
||||
console.log('setting is checked: ', isChecked, itemId)
|
||||
setIsChecked(itemId, !isChecked)
|
||||
}
|
||||
}, [activeItem, multiSelectMode, checkedItems])
|
||||
@ -493,7 +492,6 @@ export function HomeFeedContainer(): JSX.Element {
|
||||
handleCardAction('set-labels', activeItem)
|
||||
break
|
||||
case 'openNotebook':
|
||||
console.log('openNotebook: ', notebookTarget)
|
||||
handleCardAction('open-notebook', activeItem)
|
||||
break
|
||||
case 'sortDescending':
|
||||
@ -553,7 +551,6 @@ export function HomeFeedContainer(): JSX.Element {
|
||||
name: 'Mark item as read',
|
||||
shortcut: ['m', 'r'],
|
||||
perform: () => {
|
||||
console.log('mark read action')
|
||||
handleCardAction('mark-read', activeItem)
|
||||
},
|
||||
}),
|
||||
@ -612,8 +609,16 @@ export function HomeFeedContainer(): JSX.Element {
|
||||
checkedItems.splice(checkedItems.indexOf(itemId), 1)
|
||||
setCheckedItems([...checkedItems])
|
||||
}
|
||||
|
||||
if (set && multiSelectMode == 'off') {
|
||||
setMultiSelectMode('some')
|
||||
}
|
||||
|
||||
if (checkedItems.length < 1) {
|
||||
setMultiSelectMode('off')
|
||||
}
|
||||
},
|
||||
[checkedItems]
|
||||
[checkedItems, multiSelectMode, setMultiSelectMode]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
@ -806,10 +811,12 @@ type HomeFeedContentProps = {
|
||||
item: LibraryItem | undefined
|
||||
) => Promise<void>
|
||||
|
||||
multiSelectMode: MultiSelectMode
|
||||
setIsChecked: (itemId: string, set: boolean) => void
|
||||
itemIsChecked: (itemId: string) => boolean
|
||||
|
||||
multiSelectMode: MultiSelectMode
|
||||
setMultiSelectMode: (mode: MultiSelectMode) => void
|
||||
|
||||
numItemsSelected: number
|
||||
|
||||
performMultiSelectAction: (action: BulkAction, labelIds?: string[]) => void
|
||||
@ -861,7 +868,6 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
|
||||
setShowAddLinkModal={props.setShowAddLinkModal}
|
||||
searchTerm={props.searchTerm}
|
||||
applySearchQuery={(searchQuery: string) => {
|
||||
console.log('searching with searchQuery: ', searchQuery)
|
||||
props.applySearchQuery(searchQuery)
|
||||
}}
|
||||
showFilterMenu={showFilterMenu}
|
||||
@ -880,7 +886,6 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
|
||||
<LibraryItemsLayout
|
||||
viewer={viewerData?.me}
|
||||
layout={layout}
|
||||
inMultiSelect={props.multiSelectMode !== 'off'}
|
||||
isChecked={props.itemIsChecked}
|
||||
{...props}
|
||||
/>
|
||||
@ -897,7 +902,9 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
|
||||
type LibraryItemsLayoutProps = {
|
||||
layout: LayoutType
|
||||
viewer?: UserBasicData
|
||||
inMultiSelect: boolean
|
||||
|
||||
multiSelectMode: MultiSelectMode
|
||||
setMultiSelectMode: (mode: MultiSelectMode) => void
|
||||
|
||||
isChecked: (itemId: string) => boolean
|
||||
setIsChecked: (itemId: string, set: boolean) => void
|
||||
@ -970,7 +977,7 @@ function LibraryItemsLayout(props: LibraryItemsLayoutProps): JSX.Element {
|
||||
setLinkToUnsubscribe={props.setLinkToUnsubscribe}
|
||||
setShowRemoveLinkConfirmation={setShowRemoveLinkConfirmation}
|
||||
actionHandler={props.actionHandler}
|
||||
inMultiSelect={props.inMultiSelect}
|
||||
multiSelectMode={props.multiSelectMode}
|
||||
/>
|
||||
)}
|
||||
<HStack
|
||||
@ -1024,7 +1031,8 @@ function LibraryItemsLayout(props: LibraryItemsLayoutProps): JSX.Element {
|
||||
item={props.linkToRemove?.node}
|
||||
viewer={props.viewer}
|
||||
layout="GRID_LAYOUT"
|
||||
inMultiSelect={false}
|
||||
multiSelectMode={props.multiSelectMode}
|
||||
setMultiSelectMode={props.setMultiSelectMode}
|
||||
isChecked={false}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
setIsChecked={() => {}}
|
||||
@ -1095,9 +1103,9 @@ type LibraryItemsProps = {
|
||||
setLinkToUnsubscribe: (set: LibraryItem | undefined) => void
|
||||
setShowRemoveLinkConfirmation: (show: true) => void
|
||||
|
||||
inMultiSelect: boolean
|
||||
isChecked: (itemId: string) => boolean
|
||||
setIsChecked: (itemId: string, set: boolean) => void
|
||||
multiSelectMode: MultiSelectMode
|
||||
|
||||
actionHandler: (
|
||||
action: LinkedItemCardAction,
|
||||
@ -1186,7 +1194,7 @@ function LibraryItems(props: LibraryItemsProps): JSX.Element {
|
||||
viewer={props.viewer}
|
||||
isChecked={props.isChecked(linkedItem.node.id)}
|
||||
setIsChecked={props.setIsChecked}
|
||||
inMultiSelect={props.inMultiSelect}
|
||||
multiSelectMode={props.multiSelectMode}
|
||||
handleAction={(action: LinkedItemCardAction) => {
|
||||
if (action === 'delete') {
|
||||
props.setShowRemoveLinkConfirmation(true)
|
||||
|
||||
@ -28,6 +28,7 @@ import { TrashIcon } from '../../elements/icons/TrashIcon'
|
||||
import { LabelIcon } from '../../elements/icons/LabelIcon'
|
||||
import { ListViewIcon } from '../../elements/icons/ListViewIcon'
|
||||
import { GridViewIcon } from '../../elements/icons/GridViewIcon'
|
||||
import { CaretDownIcon } from '../../elements/icons/CaretDownIcon'
|
||||
|
||||
export type MultiSelectMode = 'off' | 'none' | 'some' | 'visible' | 'search'
|
||||
|
||||
@ -377,9 +378,7 @@ type ControlButtonBoxProps = {
|
||||
applySearchQuery: (searchQuery: string) => void
|
||||
}
|
||||
|
||||
function MultiSelectControlButtonBox(
|
||||
props: ControlButtonBoxProps
|
||||
): JSX.Element {
|
||||
function MultiSelectControls(props: ControlButtonBoxProps): JSX.Element {
|
||||
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
||||
const [showLabelsModal, setShowLabelsModal] = useState(false)
|
||||
|
||||
@ -498,7 +497,7 @@ function SearchControlButtonBox(
|
||||
)
|
||||
}
|
||||
|
||||
function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
|
||||
const MuliSelectControl = (props: ControlButtonBoxProps): JSX.Element => {
|
||||
const [isChecked, setIsChecked] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
@ -507,6 +506,69 @@ function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
|
||||
}
|
||||
}, [props.multiSelectMode])
|
||||
|
||||
return (
|
||||
<Box
|
||||
css={{
|
||||
display: 'flex',
|
||||
padding: '10px',
|
||||
height: '38px',
|
||||
maxWidth: '521px',
|
||||
bg: '$thLibrarySearchbox',
|
||||
borderRadius: '6px',
|
||||
|
||||
boxShadow: '0px 1px 2px 0px rgba(0, 0, 0, 0.05);',
|
||||
'@mdDown': {
|
||||
mx: '5px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SpanBox
|
||||
css={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
gap: '5px',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<CardCheckbox
|
||||
isChecked={isChecked}
|
||||
handleChanged={() => {
|
||||
const newValue = !isChecked
|
||||
props.setMultiSelectMode(newValue ? 'visible' : 'off')
|
||||
setIsChecked(newValue)
|
||||
}}
|
||||
/>
|
||||
<SpanBox css={{ display: 'flex', pb: '2px' }}>
|
||||
<Dropdown
|
||||
triggerElement={
|
||||
<CaretDownIcon
|
||||
size={9}
|
||||
color={theme.colors.graySolid.toString()}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<DropdownOption
|
||||
onSelect={() => {
|
||||
setIsChecked(true)
|
||||
props.setMultiSelectMode('visible')
|
||||
}}
|
||||
title="All"
|
||||
/>
|
||||
{/* <DropdownOption
|
||||
onSelect={() => {
|
||||
setIsChecked(true)
|
||||
props.setMultiSelectMode('search')
|
||||
}}
|
||||
title="All matching search"
|
||||
/> */}
|
||||
</Dropdown>
|
||||
</SpanBox>
|
||||
</SpanBox>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<HStack
|
||||
@ -519,7 +581,7 @@ function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
|
||||
},
|
||||
width: '95%',
|
||||
'@media (min-width: 930px)': {
|
||||
width: '640px',
|
||||
width: '660px',
|
||||
},
|
||||
'@media (min-width: 1280px)': {
|
||||
width: '1000px',
|
||||
@ -529,6 +591,7 @@ function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<MuliSelectControl {...props} />
|
||||
{props.multiSelectMode !== 'off' && (
|
||||
<SpanBox
|
||||
css={{
|
||||
@ -541,42 +604,9 @@ function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CardCheckbox
|
||||
isChecked={isChecked}
|
||||
handleChanged={() => {
|
||||
const newValue = !isChecked
|
||||
props.setMultiSelectMode(newValue ? 'visible' : 'none')
|
||||
setIsChecked(newValue)
|
||||
}}
|
||||
/>
|
||||
<SpanBox css={{ pt: '2px' }}>
|
||||
<Dropdown
|
||||
triggerElement={
|
||||
<CaretDown
|
||||
size={15}
|
||||
color={theme.colors.graySolid.toString()}
|
||||
weight="fill"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<DropdownOption
|
||||
onSelect={() => {
|
||||
setIsChecked(true)
|
||||
props.setMultiSelectMode('visible')
|
||||
}}
|
||||
title="All"
|
||||
/>
|
||||
<DropdownOption
|
||||
onSelect={() => {
|
||||
setIsChecked(true)
|
||||
props.setMultiSelectMode('search')
|
||||
}}
|
||||
title="All matching search"
|
||||
/>
|
||||
</Dropdown>
|
||||
</SpanBox>
|
||||
<SpanBox
|
||||
css={{
|
||||
color: '#55B938',
|
||||
paddingLeft: '5px',
|
||||
fontSize: '12px',
|
||||
fontWeight: '600',
|
||||
@ -588,7 +618,9 @@ function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
|
||||
>
|
||||
{props.numItemsSelected}{' '}
|
||||
<SpanBox
|
||||
css={{ '@media (max-width: 1280px)': { display: 'none' } }}
|
||||
css={{
|
||||
'@media (max-width: 1280px)': { display: 'none' },
|
||||
}}
|
||||
>
|
||||
selected
|
||||
</SpanBox>
|
||||
@ -597,7 +629,7 @@ function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
|
||||
)}
|
||||
{props.multiSelectMode !== 'off' ? (
|
||||
<>
|
||||
<MultiSelectControlButtonBox {...props} />
|
||||
<MultiSelectControls {...props} />
|
||||
<SpanBox css={{ flex: 1 }}></SpanBox>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@ -6,7 +6,7 @@ const ContentSecurityPolicy = `
|
||||
font-src 'self' data: cdn.jsdelivr.net https://js.intercomcdn.com https://fonts.intercomcdn.com;
|
||||
form-action 'self' ${process.env.NEXT_PUBLIC_SERVER_BASE_URL} https://getpocket.com/auth/authorize https://intercom.help https://api-iam.intercom.io https://api-iam.eu.intercom.io https://api-iam.au.intercom.io;
|
||||
frame-ancestors 'none';
|
||||
frame-src self accounts.google.com platform.twitter.com www.youtube.com www.youtube-nocookie.com;
|
||||
frame-src 'self' accounts.google.com platform.twitter.com www.youtube.com www.youtube-nocookie.com;
|
||||
manifest-src 'self';
|
||||
script-src 'self' 'unsafe-inline' 'unsafe-eval' accounts.google.com widget.intercom.io js.intercomcdn.com platform.twitter.com cdnjs.cloudflare.com cdn.jsdelivr.net cdn.segment.com;
|
||||
style-src 'self' 'unsafe-inline' accounts.google.com cdnjs.cloudflare.com;
|
||||
|
||||
Reference in New Issue
Block a user