From 1067726a7a2695527618e6dfbcbd02fbb1a51626 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 8 Mar 2023 17:48:30 +0800 Subject: [PATCH] Sort highlights, make the notes editable --- .../LibraryCards/LibraryHighlightGridCard.tsx | 44 +++- .../templates/article/NotebookModal.tsx | 86 +------ .../templates/homeFeed/HighlightItem.tsx | 241 ++++++++++++------ .../templates/homeFeed/HighlightsLayout.tsx | 2 + 4 files changed, 199 insertions(+), 174 deletions(-) diff --git a/packages/web/components/patterns/LibraryCards/LibraryHighlightGridCard.tsx b/packages/web/components/patterns/LibraryCards/LibraryHighlightGridCard.tsx index 2358c10f3..3ad9d9fe1 100644 --- a/packages/web/components/patterns/LibraryCards/LibraryHighlightGridCard.tsx +++ b/packages/web/components/patterns/LibraryCards/LibraryHighlightGridCard.tsx @@ -1,5 +1,5 @@ import { Box, VStack, HStack } from '../../elements/LayoutPrimitives' -import { useState } from 'react' +import { useMemo, useState } from 'react' import { CaretDown, CaretUp } from 'phosphor-react' import { MetaStyle, timeAgo, TitleStyle } from './LibraryCardStyles' import { styled } from '@stitches/react' @@ -8,6 +8,7 @@ import { LibraryItemNode } from '../../../lib/networking/queries/useGetLibraryIt import { Button } from '../../elements/Button' import { theme } from '../../tokens/stitches.config' import { HighlightItem } from '../../templates/homeFeed/HighlightItem' +import { getHighlightLocation } from '../../templates/article/NotebookModal' export const GridSeparator = styled(Box, { height: '1px', @@ -23,11 +24,42 @@ type LibraryHighlightGridCardProps = { export function LibraryHighlightGridCard( props: LibraryHighlightGridCardProps ): JSX.Element { - const [isHovered, setIsHovered] = useState(false) const [expanded, setExpanded] = useState(false) const higlightCount = props.item.highlights?.length ?? 0 + const sortedHighlights = useMemo(() => { + const sorted = (a: number, b: number) => { + if (a < b) { + return -1 + } + if (a > b) { + return 1 + } + return 0 + } + + if (!props.item.highlights) { + return [] + } + + return props.item.highlights.sort((a: Highlight, b: Highlight) => { + if (a.highlightPositionPercent && b.highlightPositionPercent) { + return sorted(a.highlightPositionPercent, b.highlightPositionPercent) + } + // We do this in a try/catch because it might be an invalid diff + // With PDF it will definitely be an invalid diff. + try { + const aPos = getHighlightLocation(a.patch) + const bPos = getHighlightLocation(b.patch) + if (aPos && bPos) { + return sorted(aPos, bPos) + } + } catch {} + return a.createdAt.localeCompare(b.createdAt) + }) + }, [props.item.highlights]) + return ( { - setIsHovered(true) - }} - onMouseLeave={() => { - setIsHovered(false) - }} > {!expanded && ( - {(props.item.highlights ?? []).map((highlight) => ( + {sortedHighlights.map((highlight) => ( ) : null} {isEditing && ( - ) } - -type TextEditAreaProps = { - setIsEditing: (editing: boolean) => void - highlight: Highlight - updateHighlight: (highlight: Highlight) => void -} - -export const TextEditArea = (props: TextEditAreaProps): JSX.Element => { - const [noteContent, setNoteContent] = useState( - props.highlight.annotation ?? '' - ) - - const handleNoteContentChange = useCallback( - (event: React.ChangeEvent): void => { - setNoteContent(event.target.value) - }, - [setNoteContent] - ) - - return ( - - - - - - - - ) -} diff --git a/packages/web/components/templates/homeFeed/HighlightItem.tsx b/packages/web/components/templates/homeFeed/HighlightItem.tsx index ff2022bff..2b40a961d 100644 --- a/packages/web/components/templates/homeFeed/HighlightItem.tsx +++ b/packages/web/components/templates/homeFeed/HighlightItem.tsx @@ -1,12 +1,16 @@ import { styled } from '@stitches/react' import { useRouter } from 'next/router' import { DotsThreeVertical } from 'phosphor-react' -import { Fragment, useMemo, useState } from 'react' +import { Fragment, useCallback, useMemo, useState } from 'react' import { Highlight } from '../../../lib/networking/fragments/highlightFragment' import { Label } from '../../../lib/networking/fragments/labelFragment' +import { deleteHighlightMutation } from '../../../lib/networking/mutations/deleteHighlightMutation' +import { setLabelsForHighlight } from '../../../lib/networking/mutations/setLabelsForHighlight' import { LibraryItemNode } from '../../../lib/networking/queries/useGetLibraryItemsQuery' import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery' +import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers' import { Dropdown, DropdownOption } from '../../elements/DropdownElements' +import { HighlightNoteTextEditArea } from '../../elements/HighlightNoteTextEditArea' import { LabelChip } from '../../elements/LabelChip' import { Blockquote, @@ -16,6 +20,8 @@ import { VStack, } from '../../elements/LayoutPrimitives' import { StyledText } from '../../elements/StyledText' +import { ConfirmationModal } from '../../patterns/ConfirmationModal' +import { SetLabelsModal } from '../article/SetLabelsModal' type HighlightItemProps = { highlight: Highlight @@ -44,89 +50,162 @@ export function HighlightItem(props: HighlightItemProps): JSX.Element { [props.highlight.quote] ) + const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] = + useState(undefined) + const [labelsTarget, setLabelsTarget] = useState( + undefined + ) + const [, updateState] = useState({}) + return ( - setHover(true)} - onMouseLeave={() => setHover(false)} - > - { - if (router && props.viewer) { - const dest = `/${props.viewer}/${props.item.slug}#${props.highlight.id}` - router.push(dest) - } - event.preventDefault() - }} + <> + setHover(true)} + onMouseLeave={() => setHover(false)} > - - - {lines.map((line: string, index: number) => ( - - {line} - {index !== lines.length - 1 && ( - <> -
-
- - )} -
- ))} -
- - {props.highlight.labels?.map((label: Label, index: number) => ( - - ))} - -
- - setIsEditing(true)} + alignment="start" + distribution="start" > - {props.highlight.annotation - ? props.highlight.annotation - : 'Add your notes...'} - -
- - - -
+ { + if (router && props.viewer) { + const dest = `/${props.viewer}/${props.item.slug}#${props.highlight.id}` + router.push(dest) + } + event.preventDefault() + }} + > + + {lines.map((line: string, index: number) => ( + + {line} + {index !== lines.length - 1 && ( + <> +
+
+ + )} +
+ ))} +
+ + {props.highlight.labels?.map((label: Label, index: number) => ( + + ))} + +
+ + {!isEditing && ( + setIsEditing(true)} + > + {props.highlight.annotation + ? props.highlight.annotation + : 'Add your notes...'} + + )} + {isEditing && ( + {}} + /> + )} +
+ + + + + {showConfirmDeleteHighlightId && ( + { + setShowConfirmDeleteHighlightId(undefined) + const result = await deleteHighlightMutation( + showConfirmDeleteHighlightId + ) + if (result) { + showSuccessToast('Highlight deleted') + } else { + showErrorToast('Error deleting highlight') + } + }} + onOpenChange={() => setShowConfirmDeleteHighlightId(undefined)} + /> + )} + {labelsTarget && ( + { + const result = setLabelsForHighlight( + labelsTarget.id, + labels.map((label) => label.id) + ) + return result + }} + /> + )} + ) } -function HighlightsMenu(): JSX.Element { +type HighlightsMenuProps = { + highlight: Highlight + + setLabelsTarget: (target: Highlight) => void + setShowConfirmDeleteHighlightId: (set: string) => void +} + +function HighlightsMenu(props: HighlightsMenuProps): JSX.Element { + const copyHighlight = useCallback(() => { + ;(async () => { + await navigator.clipboard.writeText(props.highlight.quote) + showSuccessToast('Highlight copied') + })() + }, [props.highlight]) + return ( { - console.log('copy') + onSelect={async () => { + copyHighlight() }} title="Copy" /> { - console.log('labels') + props.setLabelsTarget(props.highlight) }} title="Labels" /> - { - console.log('delete') + props.setShowConfirmDeleteHighlightId(props.highlight.id) }} title="Delete" - /> + /> */} ) } diff --git a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx index 5fe514e0a..5f99162d5 100644 --- a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx +++ b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx @@ -1,5 +1,6 @@ import { DotsThreeVertical, HighlighterCircle } from 'phosphor-react' import { useEffect, useState } from 'react' +import { Toaster } from 'react-hot-toast' import { LibraryItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery' import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery' @@ -46,6 +47,7 @@ export function HighlightItemsLayout( distribution="start" alignment="start" > +