diff --git a/packages/web/components/elements/ProgressBar.tsx b/packages/web/components/elements/ProgressBar.tsx
index cc39172a5..a367bcd51 100644
--- a/packages/web/components/elements/ProgressBar.tsx
+++ b/packages/web/components/elements/ProgressBar.tsx
@@ -11,9 +11,8 @@ export function ProgressBar(props: ProgressBarProps): JSX.Element {
return (
diff --git a/packages/web/components/patterns/LibraryCards/CardTypes.tsx b/packages/web/components/patterns/LibraryCards/CardTypes.tsx
index 2c9077661..799509cb2 100644
--- a/packages/web/components/patterns/LibraryCards/CardTypes.tsx
+++ b/packages/web/components/patterns/LibraryCards/CardTypes.tsx
@@ -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
}
diff --git a/packages/web/components/patterns/LibraryCards/LibraryCardStyles.tsx b/packages/web/components/patterns/LibraryCards/LibraryCardStyles.tsx
index cf421f402..19fdf4e4a 100644
--- a/packages/web/components/patterns/LibraryCards/LibraryCardStyles.tsx
+++ b/packages/web/components/patterns/LibraryCards/LibraryCardStyles.tsx
@@ -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 ? (
- <>
- {` | `}
-
- {`${Math.round(props.item.readingProgressPercent)}%`}
-
- >
- ) : null}
)
}
@@ -156,10 +148,19 @@ type CardCheckBoxProps = {
export function CardCheckbox(props: CardCheckBoxProps): JSX.Element {
return (
-
+
)
}
diff --git a/packages/web/components/patterns/LibraryCards/LibraryGridCard.tsx b/packages/web/components/patterns/LibraryCards/LibraryGridCard.tsx
index b87b96e82..9ea58c51f 100644
--- a/packages/web/components/patterns/LibraryCards/LibraryGridCard.tsx
+++ b/packages/web/components/patterns/LibraryCards/LibraryGridCard.tsx
@@ -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 ? (
-
- ) : (
- <>
- {!isTouchScreenDevice() && (
-
-
-
- )}
-
-
-
-
-
- >
+ {!isTouchScreenDevice() && (
+
+
+
)}
+
+
+
+
+
)
}
@@ -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 ? (
-
- ) : (
- {
- setDisplayFallback(true)
- }}
- />
+ return (
+ <>
+ {(props.readingProgress ?? 0) > 0 && (
+
+ )}
+ {displayFallback ? (
+
+ ) : (
+ {
+ 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 (
-
- {props.inMultiSelect ? (
-
-
-
- ) : (
-
- setMenuOpen(open)}
- actionHandler={props.handleAction}
- triggerElement={
-
- }
- />
-
- )}
+
+
+
+
+
+ setMenuOpen(open)}
+ actionHandler={props.handleAction}
+ triggerElement={
+
+ }
+ />
+
{
{
const [menuOpen, setMenuOpen] = useState(false)
- console.log(' props.isHovered || menuOpen', props.isHovered, menuOpen)
-
return (
- {props.inMultiSelect ? (
-
- ) : (
- <>
- {!isTouchScreenDevice() && (
-
-
-
- )}
-
-
-
-
-
- >
+ {!isTouchScreenDevice() && (
+
+
+
)}
+
+
+
+
+
)
}
+type ProgressBarOverlayProps = {
+ top: number
+ width: string
+ value: number
+ bottomRadius: string
+}
+
+export const ProgressBarOverlay = (
+ props: ProgressBarOverlayProps
+): JSX.Element => {
+ return (
+
+
+
+ )
+}
+
+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 && (
+
+ )}
+ {displayFallback ? (
+
+ ) : (
+ {
+ setDisplayFallback(true)
+ }}
+ />
+ )}
+ >
+ )
+}
+
export function LibraryListCardContent(
props: LinkedItemCardProps
): JSX.Element {
@@ -140,57 +216,96 @@ export function LibraryListCardContent(
}, [isChecked, setIsChecked, item])
return (
- <>
-
-
- {props.inMultiSelect ? (
-
-
-
- ) : (
-
- setMenuOpen(open)}
- actionHandler={props.handleAction}
- triggerElement={
-
- }
- />
-
- )}
-
+
+ input': {
+ p: '0px',
+ m: '0px',
+ },
+ }}
+ >
+
+
+
+
+
+
+ setMenuOpen(open)}
+ actionHandler={props.handleAction}
+ triggerElement={
+
+ }
+ />
+
+
-
- {props.item.title}
-
-
- {props.item.author}
- {props.item.author && originText && ' | '}
- {originText}
-
+
+
+
+
+ {props.item.title}
+
+ {props.item.author}
+ {props.item.author && originText && ' | '}
+
+ {originText}
+
+
+
- >
+
)
}
diff --git a/packages/web/components/templates/PrimaryLayout.tsx b/packages/web/components/templates/PrimaryLayout.tsx
index 74bd10dbd..c7368b2ae 100644
--- a/packages/web/components/templates/PrimaryLayout.tsx
+++ b/packages/web/components/templates/PrimaryLayout.tsx
@@ -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
diff --git a/packages/web/components/templates/UploadModal.tsx b/packages/web/components/templates/UploadModal.tsx
index 8fac6c53e..021210fa3 100644
--- a/packages/web/components/templates/UploadModal.tsx
+++ b/packages/web/components/templates/UploadModal.tsx
@@ -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')
diff --git a/packages/web/components/templates/article/Article.tsx b/packages/web/components/templates/article/Article.tsx
index 2b9c44da8..2ad51d320 100644
--- a/packages/web/components/templates/article/Article.tsx
+++ b/packages/web/components/templates/article/Article.tsx
@@ -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`)
diff --git a/packages/web/components/templates/article/HighlightsLayer.tsx b/packages/web/components/templates/article/HighlightsLayer.tsx
index 032b0838e..f095918f3 100644
--- a/packages/web/components/templates/article/HighlightsLayer.tsx
+++ b/packages/web/components/templates/article/HighlightsLayer.tsx
@@ -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({
diff --git a/packages/web/components/templates/article/Notebook.tsx b/packages/web/components/templates/article/Notebook.tsx
index 671b342ee..a4c1c0011 100644
--- a/packages/web/components/templates/article/Notebook.tsx
+++ b/packages/web/components/templates/article/Notebook.tsx
@@ -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 () => {
diff --git a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx
index 8c886fc91..57cba74b5 100644
--- a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx
+++ b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx
@@ -84,11 +84,13 @@ export function HomeFeedContainer(): JSX.Element {
const gridContainerRef = useRef(null)
- const [labelsTarget, setLabelsTarget] =
- useState(undefined)
+ const [labelsTarget, setLabelsTarget] = useState(
+ undefined
+ )
- const [notebookTarget, setNotebookTarget] =
- useState(undefined)
+ const [notebookTarget, setNotebookTarget] = useState(
+ undefined
+ )
const [showAddLinkModal, setShowAddLinkModal] = useState(false)
const [showEditTitleModal, setShowEditTitleModal] = useState(false)
@@ -372,15 +374,12 @@ export function HomeFeedContainer(): JSX.Element {
const [multiSelectMode, setMultiSelectMode] = useState('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
- 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 {
@@ -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}
/>
)}
{}}
@@ -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)
diff --git a/packages/web/components/templates/homeFeed/LibraryHeader.tsx b/packages/web/components/templates/homeFeed/LibraryHeader.tsx
index a77d468cf..cb823203b 100644
--- a/packages/web/components/templates/homeFeed/LibraryHeader.tsx
+++ b/packages/web/components/templates/homeFeed/LibraryHeader.tsx
@@ -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 (
+
+
+ {
+ const newValue = !isChecked
+ props.setMultiSelectMode(newValue ? 'visible' : 'off')
+ setIsChecked(newValue)
+ }}
+ />
+
+
+ }
+ >
+ {
+ setIsChecked(true)
+ props.setMultiSelectMode('visible')
+ }}
+ title="All"
+ />
+ {/* {
+ setIsChecked(true)
+ props.setMultiSelectMode('search')
+ }}
+ title="All matching search"
+ /> */}
+
+
+
+
+ )
+}
+
+function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
return (
<>
+
{props.multiSelectMode !== 'off' && (
- {
- const newValue = !isChecked
- props.setMultiSelectMode(newValue ? 'visible' : 'none')
- setIsChecked(newValue)
- }}
- />
-
-
- }
- >
- {
- setIsChecked(true)
- props.setMultiSelectMode('visible')
- }}
- title="All"
- />
- {
- setIsChecked(true)
- props.setMultiSelectMode('search')
- }}
- title="All matching search"
- />
-
-
{props.numItemsSelected}{' '}
selected
@@ -597,7 +629,7 @@ function ControlButtonBox(props: ControlButtonBoxProps): JSX.Element {
)}
{props.multiSelectMode !== 'off' ? (
<>
-
+
>
) : (
diff --git a/packages/web/next.config.js b/packages/web/next.config.js
index e6ebad8fa..9477bdcc6 100644
--- a/packages/web/next.config.js
+++ b/packages/web/next.config.js
@@ -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;