From 33e301cae63a4e551379b1081814f79354989ca9 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 24 Mar 2023 20:18:37 +0800 Subject: [PATCH] Handle changes in the modal notebook --- .../components/patterns/HighlightNoteBox.tsx | 324 ------------------ .../web/components/patterns/HighlightView.tsx | 4 +- .../components/patterns/HighlightViewNote.tsx | 205 ----------- .../templates/article/HighlightsLayer.tsx | 24 +- .../templates/article/NotebookModal.tsx | 66 ++-- .../web/lib/highlights/deleteHighlight.ts | 10 +- 6 files changed, 59 insertions(+), 574 deletions(-) delete mode 100644 packages/web/components/patterns/HighlightNoteBox.tsx delete mode 100644 packages/web/components/patterns/HighlightViewNote.tsx diff --git a/packages/web/components/patterns/HighlightNoteBox.tsx b/packages/web/components/patterns/HighlightNoteBox.tsx deleted file mode 100644 index 8ce55bc4c..000000000 --- a/packages/web/components/patterns/HighlightNoteBox.tsx +++ /dev/null @@ -1,324 +0,0 @@ -/* eslint-disable react/no-children-prop */ -import { - ChangeEvent, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react' -import { formattedShortTime } from '../../lib/dateFormatting' -import { HStack, SpanBox, VStack } from '../elements/LayoutPrimitives' - -import MarkdownIt from 'markdown-it' -import MdEditor from 'react-markdown-editor-lite' -import 'react-markdown-editor-lite/lib/index.css' -import ReactMarkdown from 'react-markdown' -import throttle from 'lodash/throttle' -import { Highlight } from '../../lib/networking/fragments/highlightFragment' - -const mdParser = new MarkdownIt() - -type NoteSectionProps = { - placeHolder: string - mode: 'edit' | 'preview' - - sizeMode: 'normal' | 'maximized' - setEditMode: (set: 'edit' | 'preview') => void - - text: string | undefined - saveText: (text: string, completed: (success: boolean) => void) => void -} - -export function HighlightNoteBox(props: NoteSectionProps): JSX.Element { - const [lastSaved, setLastSaved] = useState(undefined) - const [lastChanged, setLastChanged] = useState(undefined) - const [errorSaving, setErrorSaving] = useState(undefined) - - const saveText = useCallback( - (text, updateTime) => { - props.saveText(text, (success) => { - if (success) { - setLastSaved(updateTime) - } - }) - }, - [props] - ) - - const saveRef = useRef(saveText) - - useEffect(() => { - saveRef.current = saveText - }, [lastSaved, lastChanged]) - - const debouncedSave = useMemo< - (text: string, updateTime: Date) => void - >(() => { - const func = (text: string, updateTime: Date) => { - saveRef.current?.(text, updateTime) - } - return throttle(func, 3000) - }, []) - - const handleEditorChange = useCallback( - ( - data: { text: string; html: string }, - event?: ChangeEvent | undefined - ) => { - if (event) { - event.preventDefault() - } - - const updateTime = new Date() - setLastChanged(updateTime) - debouncedSave(data.text, updateTime) - }, - [lastSaved, lastChanged] - ) - - return ( - <> - {props.mode == 'edit' ? ( - .section': { - borderRight: 'unset', - }, - '.rc-md-editor .editor-container .sec-md .input': { - padding: '10px', - borderRadius: '5px', - }, - }} - > - mdParser.render(text)} - onChange={handleEditorChange} - /> - - {errorSaving && ( - - {errorSaving} - - )} - {lastSaved !== undefined ? ( - <> - {lastChanged === lastSaved - ? 'Saved' - : `Last saved ${formattedShortTime(lastSaved.toISOString())}`} - - ) : null} - - - ) : ( - <> - *': { - m: '0px', - }, - }} - onClick={() => props.setEditMode('edit')} - > - - - - )} - - ) -} - -type MarkdownNote = { - placeHolder: string - mode: 'edit' | 'preview' - - sizeMode: 'normal' | 'maximized' - setEditMode: (set: 'edit' | 'preview') => void - - highlight: Highlight - - defaultText: string | undefined - handleEditorChange: (data: { text: string; html: string }) => void -} - -export function MarkdownNote(props: MarkdownNote): JSX.Element { - const [lastSaved, setLastSaved] = useState(undefined) - const [lastChanged, setLastChanged] = useState(undefined) - const [errorSaving, setErrorSaving] = useState(undefined) - - return ( - <> - {props.mode == 'edit' ? ( - .section': { - borderRight: 'unset', - }, - '.rc-md-editor .editor-container .sec-md .input': { - padding: '10px', - borderRadius: '5px', - }, - }} - > - mdParser.render(text)} - onChange={props.handleEditorChange} - /> - - {errorSaving && ( - - {errorSaving} - - )} - {lastSaved !== undefined ? ( - <> - {lastChanged === lastSaved - ? 'Saved' - : `Last saved ${formattedShortTime(lastSaved.toISOString())}`} - - ) : null} - - - ) : ( - <> - *': { - m: '0px', - }, - }} - onClick={() => props.setEditMode('edit')} - > - - - - )} - - ) -} diff --git a/packages/web/components/patterns/HighlightView.tsx b/packages/web/components/patterns/HighlightView.tsx index 50c42cbf2..c64fd1785 100644 --- a/packages/web/components/patterns/HighlightView.tsx +++ b/packages/web/components/patterns/HighlightView.tsx @@ -9,8 +9,8 @@ import { SpanBox, HStack, } from '../elements/LayoutPrimitives' -import { styled, theme } from '../tokens/stitches.config' -import { HighlightViewNote } from './HighlightViewNote' +import { styled } from '../tokens/stitches.config' +import { HighlightViewNote } from './HighlightNotes' type HighlightViewProps = { highlight: Highlight diff --git a/packages/web/components/patterns/HighlightViewNote.tsx b/packages/web/components/patterns/HighlightViewNote.tsx deleted file mode 100644 index d0aaffa5e..000000000 --- a/packages/web/components/patterns/HighlightViewNote.tsx +++ /dev/null @@ -1,205 +0,0 @@ -/* eslint-disable react/no-children-prop */ -import { - ChangeEvent, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react' -import { formattedShortTime } from '../../lib/dateFormatting' -import { updateHighlightMutation } from '../../lib/networking/mutations/updateHighlightMutation' -import { HStack, SpanBox, VStack } from '../elements/LayoutPrimitives' - -import MarkdownIt from 'markdown-it' -import MdEditor from 'react-markdown-editor-lite' -import 'react-markdown-editor-lite/lib/index.css' -import ReactMarkdown from 'react-markdown' -import throttle from 'lodash/throttle' -import { Highlight } from '../../lib/networking/fragments/highlightFragment' - -const mdParser = new MarkdownIt() - -type HighlightViewNoteProps = { - placeHolder: string - mode: 'edit' | 'preview' - - highlight: Highlight - - sizeMode: 'normal' | 'maximized' - setEditMode: (set: 'edit' | 'preview') => void - - text: string | undefined - updateHighlight: (highlight: Highlight) => void -} - -export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element { - const [lastSaved, setLastSaved] = useState(undefined) - const [lastChanged, setLastChanged] = useState(undefined) - const [errorSaving, setErrorSaving] = useState(undefined) - - const saveText = useCallback( - (text, updateTime) => { - ;(async () => { - const success = await updateHighlightMutation({ - annotation: text, - highlightId: props.highlight?.id, - }) - if (success) { - setLastSaved(updateTime) - props.highlight.annotation = text - props.updateHighlight(props.highlight) - } else { - setErrorSaving('Error saving highlight.') - } - })() - }, - [props] - ) - - const saveRef = useRef(saveText) - - useEffect(() => { - saveRef.current = saveText - }, [lastSaved, lastChanged, saveText]) - - const debouncedSave = useMemo< - (text: string, updateTime: Date) => void - >(() => { - const func = (text: string, updateTime: Date) => { - saveRef.current?.(text, updateTime) - } - return throttle(func, 3000) - }, []) - - const handleEditorChange = useCallback( - ( - data: { text: string; html: string }, - event?: ChangeEvent | undefined - ) => { - if (event) { - event.preventDefault() - } - - const updateTime = new Date() - setLastChanged(updateTime) - debouncedSave(data.text, updateTime) - }, - [lastSaved, lastChanged, saveText] - ) - - return ( - <> - {props.mode == 'edit' ? ( - .section': { - borderRight: 'unset', - }, - '.rc-md-editor .editor-container .sec-md .input': { - padding: '10px', - borderRadius: '5px', - }, - }} - > - mdParser.render(text)} - onChange={handleEditorChange} - /> - - {errorSaving && ( - - {errorSaving} - - )} - {lastSaved !== undefined ? ( - <> - {lastChanged === lastSaved - ? 'Saved' - : `Last saved ${formattedShortTime(lastSaved.toISOString())}`} - - ) : null} - - - ) : ( - <> - *': { - m: '0px', - }, - }} - onClick={() => props.setEditMode('edit')} - > - - - - )} - - ) -} diff --git a/packages/web/components/templates/article/HighlightsLayer.tsx b/packages/web/components/templates/article/HighlightsLayer.tsx index b583ecd1b..9cb399df6 100644 --- a/packages/web/components/templates/article/HighlightsLayer.tsx +++ b/packages/web/components/templates/article/HighlightsLayer.tsx @@ -358,6 +358,24 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { [highlights, highlightLocations, openNoteModal] ) + const handleCloseNotebook = useCallback( + (updatedHighlights: Highlight[], deletedHighlights: Highlight[]) => { + props.setShowHighlightsModal(false) + + setHighlights(updatedHighlights) + + removeHighlights( + deletedHighlights.map((h) => h.id), + highlightLocations + ) + + updatedHighlights.forEach((h) => { + updateHighlightsCallback(h) + }) + }, + [highlights, highlightLocations] + ) + useEffect(() => { if (typeof window === 'undefined') { return @@ -651,11 +669,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element { props.setShowHighlightsModal(false)} - deleteHighlightAction={(highlightId: string) => { - removeHighlightCallback(highlightId) - }} - updateHighlight={updateHighlightsCallback} + onClose={handleCloseNotebook} /> ) } diff --git a/packages/web/components/templates/article/NotebookModal.tsx b/packages/web/components/templates/article/NotebookModal.tsx index 875de63bf..082dc8040 100644 --- a/packages/web/components/templates/article/NotebookModal.tsx +++ b/packages/web/components/templates/article/NotebookModal.tsx @@ -27,25 +27,13 @@ import { createHighlightMutation } from '../../../lib/networking/mutations/creat import { v4 as uuidv4 } from 'uuid' import { nanoid } from 'nanoid' import { deleteHighlightMutation } from '../../../lib/networking/mutations/deleteHighlightMutation' -import { HighlightNoteBox } from '../../patterns/HighlightNoteBox' +import { HighlightNoteBox } from '../../patterns/HighlightNotes' type NotebookModalProps = { pageId: string highlights: Highlight[] scrollToHighlight?: (arg: string) => void - updateHighlight: (highlight: Highlight) => void - deleteHighlightAction?: (highlightId: string) => void - onOpenChange: (open: boolean) => void -} - -type HighlightListReducerAction = { - type: string - highlightId?: string - createId?: string - removeId?: string - note?: string - highlight?: Highlight - highlights?: Highlight[] + onClose: (highlights: Highlight[], deletedAnnotations: Highlight[]) => void } export const getHighlightLocation = (patch: string): number | undefined => { @@ -61,6 +49,7 @@ type AnnotationInfo = { noteId: string allAnnotations: Highlight[] + deletedAnnotations: Highlight[] } export function NotebookModal(props: NotebookModalProps): JSX.Element { @@ -74,6 +63,7 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { const [notesEditMode, setNotesEditMode] = useState<'edit' | 'preview'>( 'preview' ) + const [, updateState] = useState({}) const annotationsReducer = ( @@ -97,7 +87,7 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { loaded: true, note: note, noteId: note?.id ?? state.noteId, - allAnnotations: action.allHighlights ?? [], + allAnnotations: [...(action.allHighlights ?? [])], } } case 'CREATE_NOTE': { @@ -138,8 +128,12 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { if (idx < 0) { return { ...state } } + const deleted = state.deletedAnnotations + deleted.push(state.allAnnotations[idx]) + return { ...state, + deletedAnnotations: deleted, allAnnotations: state.allAnnotations.splice(idx, 1), } } @@ -166,6 +160,7 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { note: undefined, noteId: uuidv4(), allAnnotations: [], + deletedAnnotations: [], }) useEffect(() => { @@ -243,7 +238,6 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { const handleSaveNoteText = useCallback( (text, cb: (success: boolean) => void) => { - console.log(' handleSaveNoteText: ', text, 'highlights', annotations) if (!annotations.loaded) { // We haven't loaded the user's annotations yet, so we can't // find or create their highlight note. @@ -295,14 +289,12 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { [annotations, props.pageId] ) + const handleClose = useCallback(() => { + props.onClose(annotations.allAnnotations, annotations.deletedAnnotations) + }, [annotations]) + return ( - { - console.log('CLOSING DIALOG') - props.onOpenChange(false) - }} - > + { @@ -356,7 +348,7 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { title="Delete Document Note" /> - props.onOpenChange(false)} /> + @@ -393,7 +385,6 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { { - console.log('updating highlight: ', highlight) dispatchAnnotations({ type: 'UPDATE_HIGHLIGHT', updateHighlight: highlight, @@ -436,10 +426,20 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { { - dispatchAnnotations({ - type: 'DELETE_HIGHLIGHT', - deleteHighlightId: showConfirmDeleteHighlightId, - }) + ;(async () => { + const success = await deleteHighlightMutation( + showConfirmDeleteHighlightId + ) + if (success) { + dispatchAnnotations({ + type: 'DELETE_HIGHLIGHT', + deleteHighlightId: showConfirmDeleteHighlightId, + }) + showSuccessToast('Highlight deleted.') + } else { + showErrorToast('Error deleting highlight') + } + })() setShowConfirmDeleteHighlightId(undefined) }} onOpenChange={() => setShowConfirmDeleteHighlightId(undefined)} @@ -486,7 +486,6 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { type ModalHighlightViewProps = { highlight: Highlight - showDelete: boolean scrollToHighlight?: (arg: string) => void deleteHighlightAction: () => void updateHighlight: (highlight: Highlight) => void @@ -643,11 +642,6 @@ function SizeToggle(props: SizeToggleProps): JSX.Element { }, }} onClick={(event) => { - console.log( - ' updating size mode: ', - props.mode, - props.mode == 'normal' ? 'maximized' : 'normal' - ) props.setMode(props.mode == 'normal' ? 'maximized' : 'normal') event.preventDefault() }} diff --git a/packages/web/lib/highlights/deleteHighlight.ts b/packages/web/lib/highlights/deleteHighlight.ts index 2f07e36b1..b09bdbcaa 100644 --- a/packages/web/lib/highlights/deleteHighlight.ts +++ b/packages/web/lib/highlights/deleteHighlight.ts @@ -1,7 +1,13 @@ import { HighlightLocation } from './highlightGenerator' -import { getHighlightElements, getHighlightNoteButton } from './highlightHelpers' +import { + getHighlightElements, + getHighlightNoteButton, +} from './highlightHelpers' -export function removeHighlights(ids: string[], locations: HighlightLocation[]): void { +export function removeHighlights( + ids: string[], + locations: HighlightLocation[] +): void { ids.forEach((id) => { const elements = getHighlightElements(id) const noteButtons = getHighlightNoteButton(id)