Clean up notebook options

This commit is contained in:
Jackson Harper
2023-06-23 13:47:39 +08:00
parent 15dd87db8f
commit 9f20fd4736
7 changed files with 216 additions and 348 deletions

View File

@ -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 {
<MarkdownNote
targetId={props.targetId}
placeHolder={props.placeHolder}
sizeMode={props.sizeMode}
text={props.text}
saveText={saveText}
lastSaved={lastSaved}
@ -89,7 +87,6 @@ type HighlightViewNoteProps = {
highlight: Highlight
sizeMode: 'normal' | 'maximized'
setEditMode: (set: 'edit' | 'preview') => void
text: string | undefined
@ -120,7 +117,6 @@ export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element {
<MarkdownNote
targetId={props.targetId}
placeHolder={props.placeHolder}
sizeMode={props.sizeMode}
text={props.text}
saveText={saveText}
lastSaved={lastSaved}
@ -134,8 +130,6 @@ type MarkdownNote = {
placeHolder: string
sizeMode: 'normal' | 'maximized'
text: string | undefined
fillBackground: boolean | undefined
@ -194,8 +188,6 @@ export function MarkdownNote(props: MarkdownNote): JSX.Element {
}
}, [props, editorRef])
console.log('isDark: ', isDark)
return (
<VStack
css={{

View File

@ -42,7 +42,6 @@ type NoteSectionProps = {
placeHolder: string
mode: 'edit' | 'preview'
sizeMode: 'normal' | 'maximized'
setEditMode: (set: 'edit' | 'preview') => 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<Date | undefined>(undefined)
const saveText = useCallback(
(text, updateTime) => {
props.saveText(text, (success) => {
if (success) {
setLastSaved(updateTime)
}
})
},
[props]
)
const handleClose = useCallback(() => {
console.log('onOpenChange')
}, [])
return (
<ModalRoot
defaultOpen
onOpenChange={handleClose}
css={{ width: '100%', height: '100%' }}
>
<ModalOverlay css={{ width: '100%', height: '100%' }} />
<ModalContent
css={{
bg: '$grayBg',
zIndex: '30',
width: '100%',
height: '100%',
maxHeight: 'unset',
maxWidth: 'unset',
}}
>
<VStack>
<HStack
distribution="between"
alignment="center"
css={{
width: '100%',
position: 'sticky',
top: '0px',
height: '50px',
p: '20px',
bg: '$grayBg',
zIndex: 10,
}}
>
<StyledText style="modalHeadline" css={{ color: '$thTextSubtle2' }}>
Edit Note
</StyledText>
<HStack
css={{
ml: 'auto',
cursor: 'pointer',
gap: '15px',
mr: '-5px',
}}
distribution="center"
alignment="center"
>
{/* <Dropdown triggerElement={<MenuTrigger />}>
<DropdownOption
onSelect={() => {
exportHighlights()
}}
title="Export Notebook"
/>
<DropdownOption
onSelect={() => {
setShowConfirmDeleteNote(true)
}}
title="Delete Document Note"
/>
</Dropdown> */}
<CloseButton close={handleClose} />
</HStack>
</HStack>
<SpanBox css={{ padding: '20px', width: '100%', height: '100%' }}>
<MarkdownNote
targetId={props.targetId}
placeHolder={props.placeHolder}
mode={props.mode}
sizeMode={props.sizeMode}
setEditMode={props.setEditMode}
text={props.text}
saveText={saveText}
lastSaved={lastSaved}
fillBackground={false}
/>
</SpanBox>
</VStack>
</ModalContent>
</ModalRoot>
)
}

View File

@ -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}

View File

@ -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 | string>(undefined)
const [labelsTarget, setLabelsTarget] = useState<Highlight | undefined>(
@ -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',
}}
>
<SectionTitle
@ -329,9 +327,8 @@ export function Notebook(props: NotebookProps): JSX.Element {
<ArticleNotes
mode={notesEditMode}
targetId={props.item.id}
sizeMode={props.sizeMode}
setEditMode={setNotesEditMode}
text={annotations.note?.annotation}
text={noteState.note?.annotation}
placeHolder="Add notes to this document..."
saveText={handleSaveNoteText}
/>
@ -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)
}

View File

@ -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<Highlight[] | undefined>(
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
}
}}
>
<HStack
distribution="center"
@ -128,7 +131,6 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element {
distribution="center"
alignment="center"
>
{/* <SizeToggle mode={sizeMode} setMode={setSizeMode} /> */}
<Dropdown triggerElement={<MenuTrigger />}>
<DropdownOption
onSelect={() => {
@ -146,9 +148,8 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element {
<CloseButton close={handleClose} />
</HStack>
</HStack>
<Notebook
<NotebookContent
{...props}
sizeMode={sizeMode}
viewInReader={viewInReader}
onAnnotationsChanged={handleAnnotationsChange}
showConfirmDeleteNote={showConfirmDeleteNote}

View File

@ -21,8 +21,8 @@ export const NotebookPresenter = (props: NotebookPresenterProps) => {
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) => {

View File

@ -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 {
</HStack>
<HStack css={{ width: '100%', height: '100%' }}>
{props.viewer && (
<Notebook
sizeMode="normal"
<NotebookContent
viewer={props.viewer}
item={props.item.node}
highlights={props.item.node.highlights ?? []}