Refactor for PDF highlights

This commit is contained in:
Jackson Harper
2023-03-27 14:53:33 +08:00
parent bf768293aa
commit 2a0244b612
3 changed files with 28 additions and 231 deletions

View File

@ -55,244 +55,38 @@ type AnnotationInfo = {
}
export function NotebookModal(props: NotebookModalProps): JSX.Element {
const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] =
useState<undefined | string>(undefined)
const [labelsTarget, setLabelsTarget] = useState<Highlight | undefined>(
undefined
)
const [sizeMode, setSizeMode] = useState<'normal' | 'maximized'>('normal')
const [showConfirmDeleteNote, setShowConfirmDeleteNote] = useState(false)
const [notesEditMode, setNotesEditMode] = useState<'edit' | 'preview'>(
'preview'
const [allAnnotations, setAllAnnotations] = useState<Highlight[] | undefined>(
undefined
)
const [deletedAnnotations, setDeletedAnnotations] = useState<
Highlight[] | undefined
>(undefined)
const [, updateState] = useState({})
const handleClose = useCallback(() => {
props.onClose(allAnnotations ?? [], deletedAnnotations ?? [])
}, [allAnnotations, deletedAnnotations])
const annotationsReducer = (
state: AnnotationInfo,
action: {
type: string
allHighlights?: Highlight[]
note?: Highlight | undefined
updateHighlight?: Highlight | undefined
deleteHighlightId?: string | undefined
}
) => {
switch (action.type) {
case 'RESET': {
console.log(' -- reseting highlights: ', action.allHighlights)
const note = action.allHighlights?.find((h) => h.type == 'NOTE')
return {
...state,
loaded: true,
note: note,
noteId: note?.id ?? state.noteId,
allAnnotations: [...(action.allHighlights ?? [])],
}
}
case 'CREATE_NOTE': {
if (!action.note) {
throw new Error('No note on CREATE_NOTE action')
}
return {
...state,
note: action.note,
noteId: action.note.id,
allAnnotations: [...state.allAnnotations, action.note],
}
}
case 'DELETE_NOTE': {
// If there is no note to delete, just make sure we have cleared out the note
const noteId = action.note?.id
if (!action.note?.id) {
return {
...state,
node: undefined,
noteId: uuidv4(),
}
}
const idx = state.allAnnotations.findIndex((h) => h.id === noteId)
return {
...state,
note: undefined,
noteId: uuidv4(),
allAnnotations: state.allAnnotations.splice(idx, 1),
}
}
case 'DELETE_HIGHLIGHT': {
const highlightId = action.deleteHighlightId
if (!highlightId) {
throw new Error('No highlightId for delete action.')
}
const idx = state.allAnnotations.findIndex((h) => h.id === highlightId)
if (idx < 0) {
return { ...state }
}
const deleted = state.deletedAnnotations
deleted.push(state.allAnnotations[idx])
return {
...state,
deletedAnnotations: deleted,
allAnnotations: state.allAnnotations.splice(idx, 1),
}
}
case 'UPDATE_HIGHLIGHT': {
const highlight = action.updateHighlight
if (!highlight) {
throw new Error('No highlightId for delete action.')
}
const idx = state.allAnnotations.findIndex((h) => h.id === highlight.id)
if (idx !== -1) {
state.allAnnotations[idx] = highlight
}
return {
...state,
}
}
default:
return state
}
}
const [annotations, dispatchAnnotations] = useReducer(annotationsReducer, {
loaded: false,
note: undefined,
noteId: uuidv4(),
allAnnotations: [],
deletedAnnotations: [],
})
useEffect(() => {
dispatchAnnotations({
type: 'RESET',
allHighlights: props.highlights,
})
}, [props.highlights])
const handleAnnotationsChange = useCallback(
(allAnnotations, deletedAnnotations) => {
setAllAnnotations(allAnnotations)
setDeletedAnnotations(deletedAnnotations)
},
[]
)
const exportHighlights = useCallback(() => {
;(async () => {
if (!annotations) {
if (!allAnnotations) {
showErrorToast('No highlights to export')
return
}
const markdown = highlightsAsMarkdown(annotations.allAnnotations)
const markdown = highlightsAsMarkdown(allAnnotations)
await navigator.clipboard.writeText(markdown)
showSuccessToast('Highlight copied')
})()
}, [annotations])
const deleteDocumentNote = useCallback(() => {
const note = annotations.note
if (!note) {
showErrorToast('No note found')
return
}
;(async () => {
try {
const result = await deleteHighlightMutation(note.id)
if (!result) {
throw new Error()
}
showSuccessToast('Note deleted')
dispatchAnnotations({
note,
type: 'DELETE_NOTE',
})
} catch (err) {
console.log('error deleting note', err)
showErrorToast('Error deleting note')
}
})()
}, [annotations])
const sortedHighlights = useMemo(() => {
const sorted = (a: number, b: number) => {
if (a < b) {
return -1
}
if (a > b) {
return 1
}
return 0
}
return annotations.allAnnotations
.filter((h) => h.type === 'HIGHLIGHT')
.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)
})
}, [annotations])
const handleSaveNoteText = useCallback(
(text, cb: (success: boolean) => void) => {
if (!annotations.loaded) {
// We haven't loaded the user's annotations yet, so we can't
// find or create their highlight note.
return
}
if (!annotations.note) {
const noteId = annotations.noteId
;(async () => {
const success = await createHighlightMutation({
id: noteId,
shortId: nanoid(8),
type: 'NOTE',
articleId: props.pageId,
annotation: text,
})
console.log('success creating annotation note: ', success)
if (success) {
dispatchAnnotations({
type: 'CREATE_NOTE',
note: success,
})
}
cb(!!success)
})()
return
}
if (annotations.note) {
const note = annotations.note
;(async () => {
const success = await updateHighlightMutation({
highlightId: note.id,
annotation: text,
})
console.log('success updating annotation note: ', success)
if (success) {
note.annotation = text
dispatchAnnotations({
type: 'UPDATE_NOTE',
note: note,
})
}
cb(!!success)
})()
return
}
},
[annotations, props.pageId]
)
const handleClose = useCallback(() => {
props.onClose(annotations.allAnnotations, annotations.deletedAnnotations)
}, [annotations])
}, [allAnnotations])
return (
<ModalRoot defaultOpen onOpenChange={handleClose}>
@ -352,7 +146,7 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element {
<CloseButton close={handleClose} />
</HStack>
</HStack>
<Notebook {...props} />
<Notebook {...props} onAnnotationsChanged={handleAnnotationsChange} />
</ModalContent>
</ModalRoot>
)

View File

@ -424,6 +424,7 @@ export default function PdfArticleContainer(
document.addEventListener('deleteHighlightbyId', async (event) => {
const annotationId = (event as CustomEvent).detail as string
console.log(' DELETING ANNOTATION BY ID: ', annotationId)
for (let pageIdx = 0; pageIdx < instance.totalPageCount; pageIdx++) {
const annotations = await instance.getAnnotations(pageIdx)
for (let annIdx = 0; annIdx < annotations.size; annIdx++) {
@ -432,6 +433,7 @@ export default function PdfArticleContainer(
continue
}
const storedId = annotationOmnivoreId(annotation)
console.log(' --- storedId:', storedId)
if (storedId == annotationId) {
await instance.delete(annotation)
await deleteHighlightMutation(annotationId)
@ -501,6 +503,12 @@ export default function PdfArticleContainer(
updatedHighlights,
deletedAnnotations
)
deletedAnnotations.forEach((highlight) => {
const event = new CustomEvent('deleteHighlightbyId', {
detail: highlight.id,
})
document.dispatchEvent(event)
})
props.setShowHighlightsModal(false)
}}
/>

View File

@ -403,6 +403,7 @@ function HighlightList(props: HighlightListProps): JSX.Element {
width: '100%',
color: 'thTextContrast2',
m: '0px',
pb: '5px',
}}
>
NOTEBOOK
@ -420,12 +421,6 @@ function HighlightList(props: HighlightListProps): JSX.Element {
<Notebook
pageId={props.item.node.id}
highlights={props.item.node.highlights ?? []}
onClose={(
highlights: Highlight[],
deletedAnnotations: Highlight[]
) => {
console.log('closing notebook: ', highlights, deletedAnnotations)
}}
/>
</HStack>
</VStack>