Merge pull request #2571 from omnivore-app/fix/web-library-grid-card-progress

Add progress to library grid cards
This commit is contained in:
Jackson Harper 2023-08-03 15:12:24 +08:00 committed by GitHub
commit aab8f459b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 2048 additions and 325 deletions

View file

@ -1400,7 +1400,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.0;
MARKETING_VERSION = 1.28.0;
MARKETING_VERSION = 1.29.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app;
@ -1435,7 +1435,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.0;
MARKETING_VERSION = 1.28.0;
MARKETING_VERSION = 1.29.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -1490,7 +1490,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.28.0;
MARKETING_VERSION = 1.29.0;
PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app;
PRODUCT_NAME = Omnivore;
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1831,7 +1831,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.28.0;
MARKETING_VERSION = 1.29.0;
PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app;
PRODUCT_NAME = Omnivore;
PROVISIONING_PROFILE_SPECIFIER = "";

View file

@ -275,6 +275,14 @@ struct WebReaderContainerView: View {
Spacer()
#endif
Button(
action: { showLabelsModal = true },
label: {
Image.label
}
)
.padding(.trailing, 4)
Button(
action: { showNotebookView = true },
label: {

View file

@ -17,7 +17,7 @@ export function LabelChip(props: LabelChipProps): JSX.Element {
const isDark = isDarkTheme()
const selectedBorder = isDark ? '#FFEA9F' : 'black'
const unSelectedBorder = isDark ? '#2A2A2A' : '#D9D9D9'
const unSelectedBorder = isDark ? '#2A2A2A' : '#F5F5F5'
return (
<SpanBox

View file

@ -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>

View file

@ -1,33 +0,0 @@
import { Box } from './../elements/LayoutPrimitives'
type ProgressBarVProps = {
fillPercentage: number
fillColor: string
backgroundColor: string
borderRadius: string
height?: string
}
export function ProgressBarVertical(props: ProgressBarVProps): JSX.Element {
return (
<Box
css={{
height: props.height ?? '100%',
width: '2px',
borderRadius: '$1',
overflow: 'hidden',
backgroundColor: props.backgroundColor,
mr: '12px',
}}
>
<Box
css={{
height: `${props.fillPercentage}%`,
width: '100%',
backgroundColor: props.fillColor,
borderRadius: props.borderRadius,
}}
/>
</Box>
)
}

View file

@ -0,0 +1,29 @@
/* eslint-disable functional/no-class */
/* eslint-disable functional/no-this-expression */
import { IconProps } from './IconProps'
import React from 'react'
export class CaretDownIcon extends React.Component<IconProps> {
render() {
const size = (this.props.size || 26).toString()
const color = (this.props.color || '#2A2A2A').toString()
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 9 9"
fill="none"
>
<g>
<path
d="M7.0618 1.19531C7.21941 1.19641 7.37452 1.23475 7.51449 1.30719C7.65446 1.37964 7.77533 1.48414 7.86723 1.61218C7.95913 1.74022 8.01947 1.88817 8.04332 2.04396C8.06717 2.19975 8.05386 2.35898 8.00447 2.50865L7.9818 2.57031L7.96114 2.61231L5.59781 6.68998C5.51439 6.84091 5.39337 6.9677 5.24649 7.05804C5.09961 7.14839 4.93186 7.19921 4.75954 7.20559C4.58721 7.21196 4.41617 7.17366 4.26302 7.09442C4.10986 7.01517 3.9798 6.89766 3.88547 6.75331L3.85114 6.69565L1.48447 2.61231L1.46414 2.57065C1.40411 2.42286 1.3803 2.26285 1.39471 2.10399C1.40912 1.94513 1.46132 1.79202 1.54696 1.65744C1.6326 1.52287 1.74919 1.41073 1.887 1.3304C2.02481 1.25006 2.17984 1.20386 2.33914 1.19565L2.35847 1.19531L2.37847 1.19598L2.3928 1.19531H7.0618Z"
fill={color}
/>
</g>
</svg>
)
}
}

View file

@ -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
}

File diff suppressed because it is too large Load diff

View file

@ -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 => {
@ -134,14 +134,6 @@ export function LibraryItemMetadata(
Math.round((props.item.wordsCount ?? 0) / 235)
)} min read`
: null}
{(props.showProgress && props.item.readingProgressPercent) ?? 0 > 0 ? (
<>
{``}
<SpanBox css={{ color: '#55B938' }}>
{`${Math.round(props.item.readingProgressPercent)}%`}
</SpanBox>
</>
) : null}
{highlightCount > 0
? `${highlightCount} highlight${highlightCount > 1 ? 's' : ''}`
: null}
@ -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>
)
}

View file

@ -9,7 +9,6 @@ import Link from 'next/link'
import {
AuthorInfoStyle,
CardCheckbox,
DescriptionStyle,
LibraryItemMetadata,
MetaStyle,
siteName,
@ -29,39 +28,11 @@ 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)
type ProgressBarProps = {
fillPercentage: number
fillColor: string
backgroundColor: string
borderRadius: string
}
export function ProgressBar(props: ProgressBarProps): JSX.Element {
return (
<Box
css={{
height: '4px',
width: '100%',
borderRadius: '$1',
overflow: 'hidden',
backgroundColor: props.backgroundColor,
}}
>
<Box
css={{
height: '100%',
width: `${props.fillPercentage}%`,
backgroundColor: props.fillColor,
borderRadius: props.borderRadius,
}}
/>
</Box>
)
}
export function LibraryGridCard(props: LinkedItemCardProps): JSX.Element {
const [isHovered, setIsHovered] = useState(false)
const [isOpen, setIsOpen] = useState(false)
@ -115,38 +86,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 +154,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 +200,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
@ -268,7 +261,7 @@ const LibraryGridCardContent = (props: LinkedItemCardProps): JSX.Element => {
}}
distribution="start"
>
<LibraryItemMetadata item={props.item} />
<LibraryItemMetadata item={props.item} showProgress={true} />
</HStack>
<VStack
@ -299,7 +292,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={{

View file

@ -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]',

View file

@ -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={{

View file

@ -26,6 +26,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 +59,7 @@ export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
{...getReferenceProps()}
css={{
px: '20px',
pl: '10px',
pt: '20px',
pb: '20px',
height: '100%',
@ -92,42 +97,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 +215,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 +323,6 @@ export function LibraryListCardContent(
</HStack>
</HStack>
</VStack>
</>
</HStack>
)
}

View file

@ -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')

View file

@ -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`)

View file

@ -348,7 +348,9 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
default: styles.maxWidthPercentage
? `${styles.maxWidthPercentage}%`
: 1024 - styles.margin,
small: styles.maxWidthPercentage ? `${styles.maxWidthPercentage}%` : `${120 - Math.round((styles.margin * 10) / 100)}%`,
small: styles.maxWidthPercentage
? `${styles.maxWidthPercentage}%`
: `${120 - Math.round((styles.margin * 10) / 100)}%`,
}
const recommendationsWithNotes = useMemo(() => {

View file

@ -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({

View file

@ -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 () => {

View file

@ -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,7 @@ function LibraryItemsLayout(props: LibraryItemsLayoutProps): JSX.Element {
item={props.linkToRemove?.node}
viewer={props.viewer}
layout="GRID_LAYOUT"
inMultiSelect={false}
multiSelectMode={props.multiSelectMode}
isChecked={false}
// eslint-disable-next-line @typescript-eslint/no-empty-function
setIsChecked={() => {}}
@ -1095,9 +1102,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 +1193,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)

View file

@ -203,6 +203,12 @@ function Labels(props: LibraryFilterMenuProps): JSX.Element {
initialValue: false,
})
const sortedLabels = useMemo(() => {
return labels.sort((left: Label, right: Label) =>
left.name.localeCompare(right.name)
)
}, [labels])
return (
<MenuPanel
title="Labels"
@ -212,7 +218,7 @@ function Labels(props: LibraryFilterMenuProps): JSX.Element {
window.location.href = '/settings/labels'
}}
>
{labels.slice(0, viewAll ? undefined : 4).map((item) => {
{sortedLabels.slice(0, viewAll ? undefined : 4).map((item) => {
return <LabelButton key={item.id} label={item} {...props} />
})}
<ViewAllButton state={viewAll} setState={setViewAll} />

View file

@ -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>
</>
) : (

View file

@ -36,7 +36,7 @@ export function ReaderHeader(props: ReaderHeaderProps): JSX.Element {
? '1px solid $thBorderColor'
: '1px solid transparent',
'@xlgDown': {
bg: '$readerMargin',
bg: '$readerBg',
pointerEvents: 'unset',
borderBottom: '1px solid $thBorderColor',
},

View file

@ -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;

View file

@ -462,7 +462,7 @@ export default function Home(): JSX.Element {
css={{
width: '100%',
height: '100%',
background: '$readerMargin',
background: '$readerBg',
overflow: 'scroll',
paddingTop: '80px',
'@media print': {
@ -511,7 +511,7 @@ export default function Home(): JSX.Element {
css={{
width: '100%',
height: '100%',
background: '$readerMargin',
background: '$readerBg',
overflow: 'scroll',
paddingTop: '80px',
}}