Better handling of slugs and library item lists in the cache

This commit is contained in:
Jackson Harper
2024-07-30 15:48:21 +08:00
parent 17e547b95e
commit 6c6cc8d4c4
20 changed files with 538 additions and 391 deletions

View File

@ -523,9 +523,6 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
item={props.article}
scrollToHighlight={highlightHref}
highlights={props.article.highlights}
articleTitle={title}
articleAuthor={props.article.author ?? ''}
articleId={props.article.id}
isAppleAppEmbed={props.isAppleAppEmbed}
highlightBarDisabled={props.highlightBarDisabled}
showHighlightsModal={props.showHighlightsModal}

View File

@ -11,9 +11,6 @@ import {
import PSPDFKit from 'pspdfkit'
import { Instance, HighlightAnnotation, List, Annotation, Rect } from 'pspdfkit'
import type { Highlight } from '../../../lib/networking/fragments/highlightFragment'
import { createHighlightMutation } from '../../../lib/networking/mutations/createHighlightMutation'
import { deleteHighlightMutation } from '../../../lib/networking/mutations/deleteHighlightMutation'
import { mergeHighlightMutation } from '../../../lib/networking/mutations/mergeHighlightMutation'
import { useCanShareNative } from '../../../lib/hooks/useCanShareNative'
import { pspdfKitKey } from '../../../lib/appConfig'
import { NotebookModal } from './NotebookModal'
@ -310,56 +307,6 @@ export default function EpubContainer(props: EpubContainerProps): JSX.Element {
{/* EPUB CONTAINER
<div ></div> */}
</Box>
{noteTarget && (
<HighlightNoteModal
highlight={noteTarget}
libraryItemId={props.article.id}
author={props.article.author ?? ''}
title={props.article.title}
onUpdate={(highlight: Highlight) => {
const savedHighlight = highlightsRef.current.find(
(other: Highlight) => {
return other.id == highlight.id
}
)
if (savedHighlight) {
savedHighlight.annotation = highlight.annotation
}
}}
onOpenChange={() => {
setNoteTarget(undefined)
}}
/>
)}
{props.showHighlightsModal && (
<NotebookModal
key={notebookKey}
viewer={props.viewer}
item={props.article}
onClose={(updatedHighlights, deletedAnnotations) => {
console.log(
'closed PDF notebook: ',
updatedHighlights,
deletedAnnotations
)
deletedAnnotations.forEach((highlight) => {
const event = new CustomEvent('deleteHighlightbyId', {
detail: highlight.id,
})
document.dispatchEvent(event)
})
props.setShowHighlightsModal(false)
}}
viewHighlightInReader={(highlightId) => {
const event = new CustomEvent('scrollToHighlightId', {
detail: highlightId,
})
document.dispatchEvent(event)
props.setShowHighlightsModal(false)
}}
/>
)}
</Box>
)
}

View File

@ -13,10 +13,9 @@ import { showErrorToast } from '../../../lib/toastHelpers'
import { useUpdateHighlight } from '../../../lib/networking/highlights/useItemHighlights'
type HighlightNoteModalProps = {
author: string
title: string
highlight?: Highlight
libraryItemId: string
libraryItemSlug: string
onUpdate: (updatedHighlight: Highlight) => void
onOpenChange: (open: boolean) => void
createHighlightForNote?: (note?: string) => Promise<Highlight | undefined>
@ -43,6 +42,7 @@ export function HighlightNoteModal(
try {
const result = await updateHighlight.mutateAsync({
itemId: props.libraryItemId,
slug: props.libraryItemSlug,
input: {
libraryItemId: props.libraryItemId,
highlightId: props.highlight?.id,

View File

@ -39,9 +39,6 @@ type HighlightsLayerProps = {
item: ReadableItem
highlights: Highlight[]
articleId: string
articleTitle: string
articleAuthor: string
isAppleAppEmbed: boolean
highlightBarDisabled: boolean
showHighlightsModal: boolean
@ -105,7 +102,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
const result = await createHighlight(
{
selection: selection,
articleId: props.articleId,
articleId: props.item.id,
existingHighlights: highlights,
color: options?.color,
highlightStartEndOffsets: highlightLocations,
@ -141,7 +138,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
[
highlightLocations,
highlights,
props.articleId,
props.item.id,
props.articleMutations,
setSelectionData,
]
@ -189,7 +186,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
const didDeleteHighlight =
await props.articleMutations.deleteHighlightMutation(
props.articleId,
props.item.id,
highlightId
)
@ -226,7 +223,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
updateHighlightsCallback(highlight)
;(async () => {
const update = await props.articleMutations.updateHighlightMutation({
libraryItemId: props.articleId,
libraryItemId: props.item.id,
highlightId: highlight.id,
color: color,
})
@ -718,7 +715,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
const annotation = event.annotation ?? ''
const result = await props.articleMutations.updateHighlightMutation({
libraryItemId: props.articleId,
libraryItemId: props.item.id,
highlightId: focusedHighlight.id,
annotation: event.annotation ?? '',
})
@ -800,9 +797,8 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
{highlightModalAction?.highlightModalAction == 'addComment' && (
<HighlightNoteModal
highlight={highlightModalAction.highlight}
author={props.articleAuthor}
title={props.articleTitle}
libraryItemId={props.articleId}
libraryItemId={props.item.id}
libraryItemSlug={props.item.slug}
onUpdate={updateHighlightsCallback}
onOpenChange={() =>
setHighlightModalAction({ highlightModalAction: 'none' })

View File

@ -5,10 +5,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { updateHighlightMutation } from '../../../lib/networking/mutations/updateHighlightMutation'
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
import 'react-markdown-editor-lite/lib/index.css'
import { createHighlightMutation } from '../../../lib/networking/mutations/createHighlightMutation'
import { v4 as uuidv4 } from 'uuid'
import { nanoid } from 'nanoid'
import { deleteHighlightMutation } from '../../../lib/networking/mutations/deleteHighlightMutation'
import { HighlightViewItem } from './HighlightViewItem'
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
import { TrashIcon } from '../../elements/icons/TrashIcon'
@ -20,6 +18,11 @@ import { formattedShortTime } from '../../../lib/dateFormatting'
import { isDarkTheme } from '../../../lib/themeUpdater'
import { sortHighlights } from '../../../lib/highlights/sortHighlights'
import { useGetLibraryItemContent } from '../../../lib/networking/library_items/useLibraryItems'
import {
useCreateHighlight,
useDeleteHighlight,
useUpdateHighlight,
} from '../../../lib/networking/highlights/useItemHighlights'
type NotebookContentProps = {
viewer: UserBasicData
@ -42,6 +45,9 @@ type NoteState = {
export function NotebookContent(props: NotebookContentProps): JSX.Element {
const isDark = isDarkTheme()
const createHighlight = useCreateHighlight()
const deleteHighlight = useDeleteHighlight()
const updateHighlight = useUpdateHighlight()
const { data: article } = useGetLibraryItemContent(
props.viewer.profile.username as string,
@ -87,12 +93,16 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
noteState.current.createStarted = new Date()
;(async () => {
try {
const success = await createHighlightMutation({
id: newNoteId,
shortId: nanoid(8),
type: 'NOTE',
articleId: props.item.id,
annotation: text,
const success = await createHighlight.mutateAsync({
itemId: props.item.id,
slug: props.item.slug,
input: {
id: newNoteId,
shortId: nanoid(8),
type: 'NOTE',
articleId: props.item.id,
annotation: text,
},
})
if (success) {
noteState.current.note = success
@ -164,7 +174,11 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
highlights
?.filter((h) => h.type === 'NOTE')
.forEach(async (h) => {
const result = await deleteHighlightMutation(props.item.id, h.id)
const result = await deleteHighlight.mutateAsync({
itemId: props.item.id,
slug: props.item.slug,
highlightId: h.id,
})
if (!result) {
showErrorToast('Error deleting note')
}
@ -284,10 +298,11 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
onAccept={() => {
;(async () => {
const highlightId = showConfirmDeleteHighlightId
const success = await deleteHighlightMutation(
props.item.id,
showConfirmDeleteHighlightId
)
const success = await deleteHighlight.mutateAsync({
itemId: props.item.id,
slug: props.item.slug,
highlightId: showConfirmDeleteHighlightId,
})
if (success) {
showSuccessToast('Highlight deleted.', {
position: 'bottom-right',

View File

@ -10,9 +10,6 @@ import { isDarkTheme } from '../../../lib/themeUpdater'
import PSPDFKit from 'pspdfkit'
import { Instance, HighlightAnnotation, List, Annotation, Rect } from 'pspdfkit'
import type { Highlight } from '../../../lib/networking/fragments/highlightFragment'
import { createHighlightMutation } from '../../../lib/networking/mutations/createHighlightMutation'
import { deleteHighlightMutation } from '../../../lib/networking/mutations/deleteHighlightMutation'
import { mergeHighlightMutation } from '../../../lib/networking/mutations/mergeHighlightMutation'
import { pspdfKitKey } from '../../../lib/appConfig'
import { HighlightNoteModal } from './HighlightNoteModal'
import { showErrorToast } from '../../../lib/toastHelpers'
@ -24,6 +21,12 @@ import { NotebookHeader } from './NotebookHeader'
import useWindowDimensions from '../../../lib/hooks/useGetWindowDimensions'
import { ResizableSidebar } from './ResizableSidebar'
import { DEFAULT_HOME_PATH } from '../../../lib/navigations'
import {
useCreateHighlight,
useDeleteHighlight,
useMergeHighlight,
useUpdateHighlight,
} from '../../../lib/networking/highlights/useItemHighlights'
export type PdfArticleContainerProps = {
viewer: UserBasicData
@ -42,6 +45,10 @@ export default function PdfArticleContainer(
number | undefined
>(undefined)
const highlightsRef = useRef<Highlight[]>([])
const createHighlight = useCreateHighlight()
const deleteHighlight = useDeleteHighlight()
const mergeHighlight = useMergeHighlight()
const updateHighlight = useUpdateHighlight()
const updateItemReadStatus = useUpdateItemReadStatus()
const annotationOmnivoreId = (annotation: Annotation): string | undefined => {
@ -117,7 +124,11 @@ export default function PdfArticleContainer(
.delete(annotation)
.then(() => {
if (annotationId) {
return deleteHighlightMutation(props.article.id, annotationId)
return deleteHighlight.mutateAsync({
itemId: props.article.id,
slug: props.article.slug,
highlightId: annotationId,
})
}
})
.then(() => {
@ -218,8 +229,6 @@ export default function PdfArticleContainer(
}),
}
console.log('instnace config: ', config)
instance = await PSPDFKit.load(config)
console.log('created PDF instance', instance)
@ -233,7 +242,11 @@ export default function PdfArticleContainer(
}
const annotationId = annotationOmnivoreId(annotation)
if (annotationId) {
await deleteHighlightMutation(props.article.id, annotationId)
await deleteHighlight.mutateAsync({
itemId: props.article.id,
slug: props.article.slug,
highlightId: annotationId,
})
}
})
@ -343,16 +356,21 @@ export default function PdfArticleContainer(
if (overlapping.size === 0) {
const positionPercent = positionPercentForAnnotation(annotation)
const result = await createHighlightMutation({
id: id,
shortId: shortId,
quote: quote,
articleId: props.article.id,
prefix: surroundingText.prefix,
suffix: surroundingText.suffix,
patch: JSON.stringify(serialized),
highlightPositionPercent: positionPercent * 100,
highlightPositionAnchorIndex: annotation.pageIndex,
const result = await createHighlight.mutateAsync({
itemId: props.article.id,
slug: props.article.slug,
input: {
id: id,
shortId: shortId,
quote: quote,
articleId: props.article.id,
prefix: surroundingText.prefix,
suffix: surroundingText.suffix,
patch: JSON.stringify(serialized),
highlightPositionPercent: positionPercent * 100,
highlightPositionAnchorIndex: annotation.pageIndex,
},
})
if (result) {
highlightsRef.current.push(result)
@ -388,20 +406,24 @@ export default function PdfArticleContainer(
(ha) => (ha.customData?.omnivoreHighlight as Highlight).id
)
const positionPercent = positionPercentForAnnotation(annotation)
const result = await mergeHighlightMutation({
quote,
id,
shortId,
patch: JSON.stringify(serialized),
prefix: surroundingText.prefix,
suffix: surroundingText.suffix,
articleId: props.article.id,
overlapHighlightIdList: mergedIds.toArray(),
highlightPositionPercent: positionPercent * 100,
highlightPositionAnchorIndex: annotation.pageIndex,
const result = await mergeHighlight.mutateAsync({
itemId: props.article.id,
slug: props.article.slug,
input: {
quote,
id,
shortId,
patch: JSON.stringify(serialized),
prefix: surroundingText.prefix,
suffix: surroundingText.suffix,
articleId: props.article.id,
overlapHighlightIdList: mergedIds.toArray(),
highlightPositionPercent: positionPercent * 100,
highlightPositionAnchorIndex: annotation.pageIndex,
},
})
if (result) {
highlightsRef.current.push(result)
if (result && result.highlight) {
highlightsRef.current.push(result.highlight)
}
}
}
@ -415,10 +437,14 @@ export default function PdfArticleContainer(
Math.max(0, ((pageIndex + 1) / instance.totalPageCount) * 100)
)
await updateItemReadStatus.mutateAsync({
id: props.article.id,
force: true,
readingProgressPercent: percent,
readingProgressAnchorIndex: pageIndex,
itemId: props.article.id,
slug: props.article.slug,
input: {
id: props.article.id,
force: true,
readingProgressPercent: percent,
readingProgressAnchorIndex: pageIndex,
},
})
}
)
@ -521,7 +547,11 @@ export default function PdfArticleContainer(
const storedId = annotationOmnivoreId(annotation)
if (storedId == annotationId) {
await instance.delete(annotation)
await deleteHighlightMutation(props.article.id, annotationId)
await deleteHighlight.mutateAsync({
itemId: props.article.id,
slug: props.article.slug,
highlightId: annotationId,
})
const highlightIdx = highlightsRef.current.findIndex((value) => {
return value.id == annotationId
@ -586,8 +616,7 @@ export default function PdfArticleContainer(
<HighlightNoteModal
highlight={noteTarget}
libraryItemId={props.article.id}
author={props.article.author ?? ''}
title={props.article.title}
libraryItemSlug={props.article.slug}
onUpdate={(highlight: Highlight) => {
const savedHighlight = highlightsRef.current.find(
(other: Highlight) => {

View File

@ -4,21 +4,24 @@ import { LabelsProvider } from './SetLabelsControl'
import { SetLabelsModal } from './SetLabelsModal'
import { useSetHighlightLabels } from '../../../lib/hooks/useSetHighlightLabels'
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
import { LibraryItemNode } from '../../../lib/networking/library_items/useLibraryItems'
type SetPageLabelsModalPresenterProps = {
articleId: string
article: LabelsProvider
libraryItem: LibraryItemNode
onOpenChange: (open: boolean) => void
}
export function SetPageLabelsModalPresenter(
props: SetPageLabelsModalPresenterProps
): JSX.Element {
const [labels, dispatchLabels] = useSetPageLabels(props.articleId)
const [labels, dispatchLabels] = useSetPageLabels(
props.libraryItem.id,
props.libraryItem.slug
)
const onOpenChange = useCallback(() => {
if (props.article) {
props.article.labels = labels.labels
if (props.libraryItem) {
props.libraryItem.labels = labels.labels
}
props.onOpenChange(true)
}, [props, labels])
@ -26,13 +29,13 @@ export function SetPageLabelsModalPresenter(
useEffect(() => {
dispatchLabels({
type: 'RESET',
labels: props.article.labels ?? [],
labels: props.libraryItem.labels ?? [],
})
}, [props.article, dispatchLabels])
}, [props.libraryItem, dispatchLabels])
return (
<SetLabelsModal
provider={props.article}
provider={props.libraryItem}
selectedLabels={labels.labels}
dispatchLabels={dispatchLabels}
onOpenChange={onOpenChange}