diff --git a/packages/web/components/patterns/HighlightViewNote.tsx b/packages/web/components/patterns/HighlightViewNote.tsx new file mode 100644 index 000000000..458254f3f --- /dev/null +++ b/packages/web/components/patterns/HighlightViewNote.tsx @@ -0,0 +1,293 @@ +import { + ChangeEvent, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react' +import { formattedShortTime } from '../../lib/dateFormatting' +import { createHighlightMutation } from '../../lib/networking/mutations/createHighlightMutation' +import { updateHighlightMutation } from '../../lib/networking/mutations/updateHighlightMutation' +import { Box, 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 { v4 as uuidv4 } from 'uuid' +import { nanoid } from 'nanoid' +import throttle from 'lodash/throttle' +import { Highlight } from '../../lib/networking/fragments/highlightFragment' +import { StyledText } from '../elements/StyledText' + +const mdParser = new MarkdownIt() + +type HighlightViewNoteProps = { + placeHolder: string + mode: 'edit' | 'preview' + + highlight?: Highlight + + sizeMode: 'normal' | 'maximized' + setEditMode: (set: 'edit' | 'preview') => void +} + +export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element { + const [noteText, setNoteText] = useState('') + const [lastSaved, setLastSaved] = useState(undefined) + const [lastChanged, setLastChanged] = useState(undefined) + const [errorSaving, setErrorSaving] = useState(undefined) + + const [createStartTime, setCreateStartTime] = useState( + undefined + ) + + useEffect(() => { + setNoteText(props.highlight?.annotation ?? '') + }, [props.highlight?.annotation]) + + const highlightId = useMemo(() => { + console.log(' -- highlightId: ', props.highlight) + if (props.highlight) { + return { id: props.highlight.id, shortId: props.highlight.shortId } + } + return { id: uuidv4(), shortId: nanoid(8) } + }, [props.highlight]) + + const saveText = useCallback( + (text, updateTime) => { + ;(async () => { + console.log('calling save: ', props.highlight) + if (props.highlight) { + const success = await updateHighlightMutation({ + annotation: text, + highlightId: props.highlight?.id, + }) + if (success) { + setLastSaved(updateTime) + } else { + setErrorSaving('Error saving highlight.') + } + } else { + console.log('creating note highlight: ', highlightId) + if (!createStartTime) { + setCreateStartTime(new Date()) + + const created = await createHighlightMutation({ + type: 'NOTE', + id: highlightId.id, + articleId: props.pageId, + shortId: highlightId.shortId, + annotation: text, + }) + console.log('created highlight: ', created) + + if (created) { + setLastSaved(updateTime) + // props.dispatchList({ + // type: 'CREATE_NOTE', + // highlight: created, + // }) + } else { + console.log('unable to create note highlight') + } + } + } + })() + }, + [lastSaved, lastChanged, createStartTime] + ) + + const saveRef = useRef(saveText) + + useEffect(() => { + saveRef.current = saveText + }, [lastSaved, lastChanged, createStartTime]) + + 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() + } + + setNoteText(data.text) + + const updateTime = new Date() + setLastChanged(updateTime) + debouncedSave(data.text, updateTime) + }, + [lastSaved, lastChanged, createStartTime] + ) + + console.log(' props: ', props) + return ( + <> + {props.mode == 'edit' ? ( + .section': { + borderRight: 'unset', + }, + '.rc-md-editor .editor-container .sec-md .input': { + padding: '10px', + borderRadius: '5px', + }, + }} + > + { + console.log(' BLURRING ') + props.setEditMode('preview') + }} + canView={{ + menu: props.mode == 'edit', + md: true, + html: true, + both: false, + fullScreen: false, + hideMenu: false, + }} + plugins={[ + 'header', + 'font-bold', + 'font-italic', + 'font-underline', + 'font-strikethrough', + 'list-unordered', + 'list-ordered', + 'block-quote', + 'link', + 'auto-resize', + ]} + style={{ + width: '100%', + height: props.sizeMode == 'normal' ? '160px' : '320px', + }} + renderHTML={(text: string) => mdParser.render(text)} + onChange={handleEditorChange} + /> + + {errorSaving && ( + + {errorSaving} + + )} + {lastSaved !== undefined ? ( + <> + {lastChanged === lastSaved + ? 'Saved' + : `Last saved ${formattedShortTime(lastSaved.toISOString())}`} + + ) : null} + {/* { + props.setEditMode(!editMode) + event.preventDefault() + }} + > + {editMode ? Preview : Preview} + */} + + + ) : ( + <> + *': { + m: '0px', + }, + }} + onClick={() => props.setEditMode('edit')} + > + + + + )} + + ) +} + +// {!isEditing ? ( +// setIsEditing(true)} +// > +// {props.highlight.annotation +// ? props.highlight.annotation +// : 'Add notes to this highlight...'} +// +// ) : null} +// {isEditing && ( diff --git a/packages/web/components/templates/HighlightNoteBox.tsx b/packages/web/components/templates/HighlightNoteBox.tsx new file mode 100644 index 000000000..ed1886895 --- /dev/null +++ b/packages/web/components/templates/HighlightNoteBox.tsx @@ -0,0 +1,281 @@ +import { + ChangeEvent, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react' +import { formattedShortTime } from '../../lib/dateFormatting' +import { createHighlightMutation } from '../../lib/networking/mutations/createHighlightMutation' +import { updateHighlightMutation } from '../../lib/networking/mutations/updateHighlightMutation' +import { Box, 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 { v4 as uuidv4 } from 'uuid' +import { nanoid } from 'nanoid' +import throttle from 'lodash/throttle' +import { Highlight } from '../../lib/networking/fragments/highlightFragment' +import { StyledText } from '../elements/StyledText' + +const mdParser = new MarkdownIt() + +type NoteSectionProps = { + placeHolder: string + mode: 'edit' | 'preview' + + pageId: string + highlight?: Highlight + + sizeMode: 'normal' | 'maximized' + setEditMode: (set: 'edit' | 'preview') => void + // dispatchList: Dispatch +} + +export function HighlightNoteBox(props: NoteSectionProps): JSX.Element { + const [noteText, setNoteText] = useState('') + const [lastSaved, setLastSaved] = useState(undefined) + const [lastChanged, setLastChanged] = useState(undefined) + const [errorSaving, setErrorSaving] = useState(undefined) + + const [createStartTime, setCreateStartTime] = useState( + undefined + ) + + useEffect(() => { + setNoteText(props.highlight?.annotation ?? '') + }, [props.highlight?.annotation]) + + const highlightId = useMemo(() => { + console.log(' -- highlightId: ', props.highlight) + if (props.highlight) { + return { id: props.highlight.id, shortId: props.highlight.shortId } + } + return { id: uuidv4(), shortId: nanoid(8) } + }, [props.highlight]) + + const saveText = useCallback( + (text, updateTime) => { + ;(async () => { + console.log('calling save: ', props.highlight) + if (props.highlight) { + const success = await updateHighlightMutation({ + annotation: text, + highlightId: props.highlight?.id, + }) + if (success) { + setLastSaved(updateTime) + } else { + setErrorSaving('Error saving highlight.') + } + } else { + console.log('creating note highlight: ', highlightId) + if (!createStartTime) { + setCreateStartTime(new Date()) + + const created = await createHighlightMutation({ + type: 'NOTE', + id: highlightId.id, + articleId: props.pageId, + shortId: highlightId.shortId, + annotation: text, + }) + console.log('created highlight: ', created) + + if (created) { + setLastSaved(updateTime) + // props.dispatchList({ + // type: 'CREATE_NOTE', + // highlight: created, + // }) + } else { + console.log('unable to create note highlight') + } + } + } + })() + }, + [lastSaved, lastChanged, createStartTime] + ) + + const saveRef = useRef(saveText) + + useEffect(() => { + saveRef.current = saveText + }, [lastSaved, lastChanged, createStartTime]) + + 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() + } + + setNoteText(data.text) + + const updateTime = new Date() + setLastChanged(updateTime) + debouncedSave(data.text, updateTime) + }, + [lastSaved, lastChanged, createStartTime] + ) + + return ( + <> + {props.mode == 'edit' ? ( + .section': { + borderRight: 'unset', + }, + '.rc-md-editor .editor-container .sec-md .input': { + padding: '10px', + borderRadius: '5px', + }, + }} + > + { + console.log(' BLURRING ') + props.setEditMode('preview') + }} + canView={{ + menu: props.mode == 'edit', + md: true, + html: true, + both: false, + fullScreen: false, + hideMenu: false, + }} + plugins={[ + 'header', + 'font-bold', + 'font-italic', + 'font-underline', + 'font-strikethrough', + 'list-unordered', + 'list-ordered', + 'block-quote', + 'link', + 'auto-resize', + ]} + style={{ + width: '100%', + height: props.sizeMode == 'normal' ? '160px' : '320px', + }} + renderHTML={(text: string) => mdParser.render(text)} + onChange={handleEditorChange} + /> + + {errorSaving && ( + + {errorSaving} + + )} + {lastSaved !== undefined ? ( + <> + {lastChanged === lastSaved + ? 'Saved' + : `Last saved ${formattedShortTime(lastSaved.toISOString())}`} + + ) : null} + + + ) : ( + + *': { + m: '0px', + }, + }} + onClick={() => props.setEditMode('edit')} + > + + + + )} + + ) +} + +// {!isEditing ? ( +// setIsEditing(true)} +// > +// {props.highlight.annotation +// ? props.highlight.annotation +// : 'Add notes to this highlight...'} +// +// ) : null} +// {isEditing && (