diff --git a/packages/web/components/patterns/LibraryCards/LibraryListCard.tsx b/packages/web/components/patterns/LibraryCards/LibraryListCard.tsx new file mode 100644 index 000000000..e28e5f5c4 --- /dev/null +++ b/packages/web/components/patterns/LibraryCards/LibraryListCard.tsx @@ -0,0 +1,226 @@ +import { Box, VStack, HStack, SpanBox } from '../../elements/LayoutPrimitives' +import { LabelChip } from '../../elements/LabelChip' +import type { LinkedItemCardProps } from './CardTypes' +import { CoverImage } from '../../elements/CoverImage' +import dayjs from 'dayjs' +import relativeTime from 'dayjs/plugin/relativeTime' +import { useState } from 'react' +import { DotsThree, DotsThreeVertical } from 'phosphor-react' +import Link from 'next/link' +import { CardMenu } from '../CardMenu' + +dayjs.extend(relativeTime) + +const timeAgo = (date: string | undefined): string => { + if (!date) { + return '' + } + return dayjs(date).fromNow() +} + +const shouldHideUrl = (url: string): boolean => { + try { + const origin = new URL(url).origin + const hideHosts = ['https://storage.googleapis.com', 'https://omnivore.app'] + if (hideHosts.indexOf(origin) != -1) { + return true + } + } catch { + console.log('invalid url item', url) + } + return false +} + +const siteName = (originalArticleUrl: string, itemUrl: string): string => { + if (shouldHideUrl(originalArticleUrl)) { + return '' + } + try { + return new URL(originalArticleUrl).hostname.replace(/^www\./, '') + } catch {} + try { + return new URL(itemUrl).hostname.replace(/^www\./, '') + } catch {} + return '' +} + +// Component +export function LibraryListCard(props: LinkedItemCardProps): JSX.Element { + const [isHovered, setIsHovered] = useState(false) + const [menuOpen, setMenuOpen] = useState(false) + + const originText = + props.item.siteName || + siteName(props.item.originalArticleUrl, props.item.url) + + return ( + { + setIsHovered(true) + }} + onMouseLeave={() => { + setIsHovered(false) + }} + > + + + + + {timeAgo(props.item.savedAt)} + {` `} + {props.item.wordsCount ?? 0 > 0 + ? ` • ${Math.max( + 1, + Math.round((props.item.wordsCount ?? 0) / 235) + )} min read` + : null} + {props.item.readingProgressPercent ?? 0 > 0 ? ( + + {` • ${Math.round(props.item.readingProgressPercent)}%`} + + ) : null} + + + setMenuOpen(open)} + actionHandler={props.handleAction} + triggerElement={ + + } + /> + + + + + {props.item.title} + + + + {props.item.author} + {props.item.author && originText && ' | '} + + {originText} + + + + + + + {props.item.labels?.map(({ name, color }, index) => ( + + ))} + + + + + + + ) +}