From 9f20fd47361b0b5edba204b3ec5ccb18b360a431 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 23 Jun 2023 13:47:39 +0800 Subject: [PATCH] Clean up notebook options --- .../web/components/patterns/ArticleNotes.tsx | 8 - .../components/patterns/HighlightNotes.tsx | 118 +----- .../web/components/patterns/HighlightView.tsx | 1 - .../components/templates/article/Notebook.tsx | 385 +++++++++--------- .../templates/article/NotebookModal.tsx | 43 +- .../templates/article/NotebookPresenter.tsx | 4 +- .../templates/homeFeed/HighlightsLayout.tsx | 5 +- 7 files changed, 216 insertions(+), 348 deletions(-) diff --git a/packages/web/components/patterns/ArticleNotes.tsx b/packages/web/components/patterns/ArticleNotes.tsx index b8da7c9ef..1ee3cd4c1 100644 --- a/packages/web/components/patterns/ArticleNotes.tsx +++ b/packages/web/components/patterns/ArticleNotes.tsx @@ -47,7 +47,6 @@ type NoteSectionProps = { placeHolder: string mode: 'edit' | 'preview' - sizeMode: 'normal' | 'maximized' setEditMode: (set: 'edit' | 'preview') => void text: string | undefined @@ -72,7 +71,6 @@ export function ArticleNotes(props: NoteSectionProps): JSX.Element { void text: string | undefined @@ -120,7 +117,6 @@ export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element { void text: string | undefined @@ -68,7 +67,6 @@ export function HighlightNoteBox(props: NoteSectionProps): JSX.Element { targetId={props.targetId} placeHolder={props.placeHolder} mode={props.mode} - sizeMode={props.sizeMode} setEditMode={props.setEditMode} text={props.text} saveText={saveText} @@ -86,7 +84,6 @@ type HighlightViewNoteProps = { highlight: Highlight - sizeMode: 'normal' | 'maximized' setEditMode: (set: 'edit' | 'preview') => void text: string | undefined @@ -125,7 +122,6 @@ export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element { targetId={props.targetId} placeHolder={props.placeHolder} mode={props.mode} - sizeMode={props.sizeMode} setEditMode={props.setEditMode} text={props.text} saveText={saveText} @@ -141,7 +137,6 @@ type MarkdownNote = { placeHolder: string mode: 'edit' | 'preview' - sizeMode: 'normal' | 'maximized' setEditMode: (set: 'edit' | 'preview') => void text: string | undefined @@ -203,7 +198,7 @@ export function MarkdownNote(props: MarkdownNote): JSX.Element { ]} style={{ width: '100%', - height: props.sizeMode == 'normal' ? '160px' : '320px', + height: '160px', }} renderHTML={(text: string) => mdParser.render(text)} /> @@ -301,114 +296,3 @@ export function MarkdownNote(props: MarkdownNote): JSX.Element { ) } - -type MarkdownModalProps = { - targetId: string - - 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 MarkdownModal(props: MarkdownModalProps): JSX.Element { - const [lastSaved, setLastSaved] = useState(undefined) - - const saveText = useCallback( - (text, updateTime) => { - props.saveText(text, (success) => { - if (success) { - setLastSaved(updateTime) - } - }) - }, - [props] - ) - - const handleClose = useCallback(() => { - console.log('onOpenChange') - }, []) - - return ( - - - - - - - Edit Note - - - {/* }> - { - exportHighlights() - }} - title="Export Notebook" - /> - { - setShowConfirmDeleteNote(true) - }} - title="Delete Document Note" - /> - */} - - - - - - - - - - ) -} diff --git a/packages/web/components/patterns/HighlightView.tsx b/packages/web/components/patterns/HighlightView.tsx index 857821308..3a581f978 100644 --- a/packages/web/components/patterns/HighlightView.tsx +++ b/packages/web/components/patterns/HighlightView.tsx @@ -134,7 +134,6 @@ export function HighlightView(props: HighlightViewProps): JSX.Element { text={props.highlight.annotation} placeHolder="Add notes to this highlight..." highlight={props.highlight} - sizeMode={'normal'} mode={noteMode} setEditMode={setNoteMode} updateHighlight={props.updateHighlight} diff --git a/packages/web/components/templates/article/Notebook.tsx b/packages/web/components/templates/article/Notebook.tsx index 91af77ec8..d17596eee 100644 --- a/packages/web/components/templates/article/Notebook.tsx +++ b/packages/web/components/templates/article/Notebook.tsx @@ -28,21 +28,17 @@ import { ReadableItem } from '../../../lib/networking/queries/useGetLibraryItems import { SetHighlightLabelsModalPresenter } from './SetLabelsModalPresenter' import { Button } from '../../elements/Button' import { ArticleNotes } from '../../patterns/ArticleNotes' +import { useGetArticleQuery } from '../../../lib/networking/queries/useGetArticleQuery' -type NotebookProps = { +type NotebookContentProps = { viewer: UserBasicData item: ReadableItem highlights: Highlight[] - sizeMode: 'normal' | 'maximized' - viewInReader: (highlightId: string) => void - onAnnotationsChanged?: ( - highlights: Highlight[], - deletedAnnotations: Highlight[] - ) => void + onAnnotationsChanged?: (highlights: Highlight[]) => void showConfirmDeleteNote?: boolean setShowConfirmDeleteNote?: (show: boolean) => void @@ -62,10 +58,14 @@ type AnnotationInfo = { creatingNote: boolean allAnnotations: Highlight[] - deletedAnnotations: Highlight[] } -export function Notebook(props: NotebookProps): JSX.Element { +export function NotebookContent(props: NotebookContentProps): JSX.Element { + const { articleData, mutate } = useGetArticleQuery({ + slug: props.item.slug, + username: props.viewer.profile.username, + includeFriendsHighlights: false, + }) const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] = useState(undefined) const [labelsTarget, setLabelsTarget] = useState( @@ -74,150 +74,195 @@ export function Notebook(props: NotebookProps): JSX.Element { const [notesEditMode, setNotesEditMode] = useState<'edit' | 'preview'>( 'preview' ) - const [, updateState] = useState({}) - const annotationsReducer = ( - state: AnnotationInfo, + // const annotationsReducer = ( + // state: AnnotationInfo, + // action: { + // type: string + // allHighlights?: Highlight[] + // note?: Highlight | undefined + + // updateHighlight?: Highlight | undefined + // deleteHighlightId?: string | undefined + // } + // ) => { + // switch (action.type) { + // case 'RESET': { + // 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, + // creatingNote: false, + // allAnnotations: [...state.allAnnotations, action.note], + // } + // } + // case 'CREATING_NOTE': { + // return { + // ...state, + // creatingNote: true, + // } + // } + // 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, + // creatingNote: false, + // noteId: uuidv4(), + // allAnnotations: [], + // deletedAnnotations: [], + // }) + + // useEffect(() => { + // dispatchAnnotations({ + // type: 'RESET', + // allHighlights: props.highlights, + // }) + // }, [props.highlights]) + + // 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 noteReducer = ( + state: { + note?: Highlight + isCreating: boolean + }, action: { type: string - allHighlights?: Highlight[] - note?: Highlight | undefined - - updateHighlight?: Highlight | undefined - deleteHighlightId?: string | undefined + note?: Highlight } ) => { switch (action.type) { - case 'RESET': { - 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': { + case 'SET_NOTE': { if (!action.note) { - throw new Error('No note on CREATE_NOTE action') + console.error( + 'invalidate SET_NOTE action, no note provider', + action, + state + ) } return { ...state, note: action.note, - noteId: action.note.id, - creatingNote: false, - allAnnotations: [...state.allAnnotations, action.note], } } - case 'CREATING_NOTE': { - return { - ...state, - creatingNote: true, - } - } - 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 } + return state } - const [annotations, dispatchAnnotations] = useReducer(annotationsReducer, { - loaded: false, + const [noteState, dispatchNote] = useReducer(noteReducer, { note: undefined, - creatingNote: false, - noteId: uuidv4(), - allAnnotations: [], - deletedAnnotations: [], + isCreating: false, }) - useEffect(() => { - dispatchAnnotations({ - type: 'RESET', - allHighlights: props.highlights, - }) - }, [props.highlights]) + const highlights = useMemo(() => { + const result = articleData?.article.article.highlights + const note = result?.find((h) => h.type === 'NOTE') + if (note) { + dispatchNote({ + type: 'SET_NOTE', + note: note, + }) + } + return result + }, [articleData]) + + // const note = useMemo(() => { + // return highlights?.find((h) => h.type === 'NOTE') + // }, [highlights]) useEffect(() => { - if (props.onAnnotationsChanged) { - props.onAnnotationsChanged( - annotations.allAnnotations, - annotations.deletedAnnotations - ) + if (highlights && props.onAnnotationsChanged) { + props.onAnnotationsChanged(highlights) } - }, [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]) + }, [highlights]) const sortedHighlights = useMemo(() => { const sorted = (a: number, b: number) => { @@ -230,7 +275,7 @@ export function Notebook(props: NotebookProps): JSX.Element { return 0 } - return annotations.allAnnotations + return (highlights ?? []) .filter((h) => h.type === 'HIGHLIGHT') .sort((a: Highlight, b: Highlight) => { if (a.highlightPositionPercent && b.highlightPositionPercent) { @@ -247,59 +292,11 @@ export function Notebook(props: NotebookProps): JSX.Element { } catch {} return a.createdAt.localeCompare(b.createdAt) }) - }, [annotations]) + }, [highlights]) 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 && !annotations.creatingNote) { - dispatchAnnotations({ - type: 'CREATING_NOTE', - }) - ;(async () => { - const success = await createHighlightMutation({ - id: annotations.noteId, - shortId: nanoid(8), - type: 'NOTE', - articleId: props.item.id, - annotation: text, - }) - if (success) { - dispatchAnnotations({ - type: 'CREATE_NOTE', - note: success, - }) - } - cb(!!success) - })() - } - - 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.item] + (text, cb: (success: boolean) => void) => {}, + [highlights, props.item] ) const [articleNotesCollapsed, setArticleNotesCollapsed] = useState(false) @@ -311,8 +308,9 @@ export function Notebook(props: NotebookProps): JSX.Element { css={{ height: '100%', width: '100%', - p: '40px', + p: '20px', '@mdDown': { p: '15px' }, + background: '#F8FAFB', }} > @@ -360,10 +357,10 @@ export function Notebook(props: NotebookProps): JSX.Element { setShowConfirmDeleteHighlightId } updateHighlight={() => { - dispatchAnnotations({ - type: 'UPDATE_HIGHLIGHT', - updateHighlight: highlight, - }) + // dispatchAnnotations({ + // type: 'UPDATE_HIGHLIGHT', + // updateHighlight: highlight, + // }) }} /> ))} @@ -403,12 +400,8 @@ export function Notebook(props: NotebookProps): JSX.Element { const success = await deleteHighlightMutation( showConfirmDeleteHighlightId ) - console.log(' ConfirmationModal::DeleteHighlight', success) + mutate() if (success) { - dispatchAnnotations({ - type: 'DELETE_HIGHLIGHT', - deleteHighlightId: showConfirmDeleteHighlightId, - }) showSuccessToast('Highlight deleted.') } else { showErrorToast('Error deleting highlight') @@ -437,7 +430,7 @@ export function Notebook(props: NotebookProps): JSX.Element { message="Are you sure you want to delete the note from this document?" acceptButtonLabel="Delete" onAccept={() => { - deleteDocumentNote() + // deleteDocumentNote() if (props.setShowConfirmDeleteNote) { props.setShowConfirmDeleteNote(false) } diff --git a/packages/web/components/templates/article/NotebookModal.tsx b/packages/web/components/templates/article/NotebookModal.tsx index 7cf0d877a..260b93e52 100644 --- a/packages/web/components/templates/article/NotebookModal.tsx +++ b/packages/web/components/templates/article/NotebookModal.tsx @@ -16,7 +16,7 @@ import { diff_match_patch } from 'diff-match-patch' import { MenuTrigger } from '../../elements/MenuTrigger' import { highlightsAsMarkdown } from '../homeFeed/HighlightItem' import 'react-markdown-editor-lite/lib/index.css' -import { Notebook } from './Notebook' +import { NotebookContent } from './Notebook' import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery' import { ReadableItem } from '../../../lib/networking/queries/useGetLibraryItemsQuery' @@ -27,7 +27,7 @@ type NotebookModalProps = { highlights: Highlight[] viewHighlightInReader: (arg: string) => void - onClose: (highlights: Highlight[], deletedAnnotations: Highlight[]) => void + onClose: (highlights: Highlight[]) => void } export const getHighlightLocation = (patch: string): number | undefined => { @@ -37,26 +37,20 @@ export const getHighlightLocation = (patch: string): number | undefined => { } export function NotebookModal(props: NotebookModalProps): JSX.Element { - const [sizeMode, setSizeMode] = useState<'normal' | 'maximized'>('normal') const [showConfirmDeleteNote, setShowConfirmDeleteNote] = useState(false) const [allAnnotations, setAllAnnotations] = useState( undefined ) - const [deletedAnnotations, setDeletedAnnotations] = useState< - Highlight[] | undefined - >(undefined) const handleClose = useCallback(() => { - props.onClose(allAnnotations ?? [], deletedAnnotations ?? []) - }, [props, allAnnotations, deletedAnnotations]) + console.log('closing: ', allAnnotations) + props.onClose(allAnnotations ?? []) + }, [props, allAnnotations]) - const handleAnnotationsChange = useCallback( - (allAnnotations, deletedAnnotations) => { - setAllAnnotations(allAnnotations) - setDeletedAnnotations(deletedAnnotations) - }, - [] - ) + const handleAnnotationsChange = useCallback((allAnnotations) => { + console.log('all annotation: ', allAnnotations) + setAllAnnotations(allAnnotations) + }, []) const exportHighlights = useCallback(() => { ;(async () => { @@ -90,9 +84,9 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { overflow: 'auto', bg: '$thLibraryBackground', width: '100%', - height: sizeMode === 'normal' ? 'unset' : '100%', - maxWidth: sizeMode === 'normal' ? '748px' : '1050px', - minHeight: sizeMode === 'normal' ? '525px' : 'unset', + height: 'unset', + maxWidth: '748px', + minHeight: '525px', '@mdDown': { top: '20px', width: '100%', @@ -101,6 +95,15 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { transform: 'translate(-50%)', }, }} + onKeyUp={(event) => { + switch (event.key) { + case 'Escape': + handleClose() + event.preventDefault() + event.stopPropagation() + break + } + }} > - {/* */} }> { @@ -146,9 +148,8 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element { - { viewer={props.viewer} item={props.item} highlights={props.highlights} - onClose={(highlights: Highlight[], deletedAnnotations: Highlight[]) => { - console.log('NotebookModal: ', highlights, deletedAnnotations) + onClose={(highlights: Highlight[]) => { + console.log('NotebookModal: ', highlights) props.onClose(highlights) }} viewHighlightInReader={(highlightId) => { diff --git a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx index 6df02906b..04455fbb6 100644 --- a/packages/web/components/templates/homeFeed/HighlightsLayout.tsx +++ b/packages/web/components/templates/homeFeed/HighlightsLayout.tsx @@ -19,7 +19,7 @@ import { timeAgo, } from '../../patterns/LibraryCards/LibraryCardStyles' import { LibraryHighlightGridCard } from '../../patterns/LibraryCards/LibraryHighlightGridCard' -import { Notebook } from '../article/Notebook' +import { NotebookContent } from '../article/Notebook' import { EmptyHighlights } from './EmptyHighlights' import { HEADER_HEIGHT } from './HeaderSpacer' import { highlightsAsMarkdown } from './HighlightItem' @@ -444,8 +444,7 @@ function HighlightList(props: HighlightListProps): JSX.Element { {props.viewer && ( -