diff --git a/packages/web/components/patterns/HighlightNotes.tsx b/packages/web/components/patterns/HighlightNotes.tsx new file mode 100644 index 000000000..3206e4b47 --- /dev/null +++ b/packages/web/components/patterns/HighlightNotes.tsx @@ -0,0 +1,273 @@ +/* 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 { updateHighlightMutation } from '../../lib/networking/mutations/updateHighlightMutation' +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 saveText = useCallback( + (text, updateTime) => { + props.saveText(text, (success) => { + if (success) { + setLastSaved(updateTime) + } + }) + }, + [props] + ) + + return ( + + ) +} + +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 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) + } + })() + }, + [props] + ) + + return ( + + ) +} + +type MarkdownNote = { + placeHolder: string + mode: 'edit' | 'preview' + + sizeMode: 'normal' | 'maximized' + setEditMode: (set: 'edit' | 'preview') => void + + text: string | undefined + + lastSaved: Date | undefined + saveText: (text: string, updateTime: Date) => void +} + +export function MarkdownNote(props: MarkdownNote): JSX.Element { + const [lastChanged, setLastChanged] = useState(undefined) + const [errorSaving, setErrorSaving] = useState(undefined) + + const saveRef = useRef(props.saveText) + + useEffect(() => { + saveRef.current = props.saveText + }, [props.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) + }, + [props.lastSaved, lastChanged] + ) + + return ( + <> + {props.mode == 'edit' ? ( + .section': { + borderRight: 'unset', + }, + '.rc-md-editor .editor-container .sec-md .input': { + padding: '10px', + borderRadius: '5px', + }, + }} + onKeyDown={(event: React.KeyboardEvent) => { + if (event.code.toLowerCase() === 'escape') { + props.setEditMode('preview') + event.preventDefault() + } + }} + > + mdParser.render(text)} + onChange={handleEditorChange} + /> + + {errorSaving && ( + + {errorSaving} + + )} + {props.lastSaved !== undefined ? ( + <> + {lastChanged === props.lastSaved + ? 'Saved' + : `Last saved ${formattedShortTime( + props.lastSaved.toISOString() + )}`} + + ) : null} + + + ) : ( + <> + *': { + m: '0px', + }, + }} + onClick={() => props.setEditMode('edit')} + > + + + + )} + + ) +}