From f1e435ff0b598de9cde58a0b1e1281faebcaef4c Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 23 Jun 2023 17:53:24 +0800 Subject: [PATCH] Passing error messages for notebooks --- .../web/components/patterns/ArticleNotes.tsx | 77 ++--------- .../components/patterns/HighlightNotes.tsx | 120 +++++++++--------- .../components/patterns/RcEditorStyles.tsx | 6 + .../components/templates/article/Notebook.tsx | 114 ++++++++++++----- 4 files changed, 161 insertions(+), 156 deletions(-) diff --git a/packages/web/components/patterns/ArticleNotes.tsx b/packages/web/components/patterns/ArticleNotes.tsx index 1ee3cd4c1..c6c86f617 100644 --- a/packages/web/components/patterns/ArticleNotes.tsx +++ b/packages/web/components/patterns/ArticleNotes.tsx @@ -45,24 +45,15 @@ type NoteSectionProps = { targetId: string placeHolder: string - mode: 'edit' | 'preview' - - setEditMode: (set: 'edit' | 'preview') => void text: string | undefined - saveText: (text: string, completed: (success: boolean) => void) => void + saveText: (text: string) => void } export function ArticleNotes(props: NoteSectionProps): JSX.Element { - const [lastSaved, setLastSaved] = useState(undefined) - const saveText = useCallback( - (text, updateTime) => { - props.saveText(text, (success) => { - if (success) { - setLastSaved(updateTime) - } - }) + (text) => { + props.saveText(text) }, [props] ) @@ -73,7 +64,6 @@ export function ArticleNotes(props: NoteSectionProps): JSX.Element { placeHolder={props.placeHolder} text={props.text} saveText={saveText} - lastSaved={lastSaved} fillBackground={false} /> ) @@ -97,14 +87,14 @@ export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element { const [lastSaved, setLastSaved] = useState(undefined) const saveText = useCallback( - (text, updateTime) => { + (text) => { ;(async () => { const success = await updateHighlightMutation({ annotation: text, highlightId: props.highlight?.id, }) if (success) { - setLastSaved(updateTime) + // setLastSaved(updateTime) props.highlight.annotation = text props.updateHighlight(props.highlight) } @@ -119,7 +109,6 @@ export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element { placeHolder={props.placeHolder} text={props.text} saveText={saveText} - lastSaved={lastSaved} fillBackground={true} /> ) @@ -133,27 +122,22 @@ type MarkdownNote = { text: string | undefined fillBackground: boolean | undefined - lastSaved: Date | undefined - saveText: (text: string, updateTime: Date) => void + saveText: (text: string) => void } export function MarkdownNote(props: MarkdownNote): JSX.Element { const editorRef = useRef(null) - const [lastChanged, setLastChanged] = useState(undefined) - const [errorSaving, setErrorSaving] = useState(undefined) const isDark = isDarkTheme() const saveRef = useRef(props.saveText) useEffect(() => { saveRef.current = props.saveText - }, [props.lastSaved, lastChanged]) + }, [props]) - const debouncedSave = useMemo< - (text: string, updateTime: Date) => void - >(() => { - const func = (text: string, updateTime: Date) => { - saveRef.current?.(text, updateTime) + const debouncedSave = useMemo<(text: string) => void>(() => { + const func = (text: string) => { + saveRef.current?.(text) } return throttle(func, 3000) }, []) @@ -167,19 +151,16 @@ export function MarkdownNote(props: MarkdownNote): JSX.Element { event.preventDefault() } - const updateTime = new Date() - setLastChanged(updateTime) - localStorage.setItem(`note-${props.targetId}`, JSON.stringify(data)) - debouncedSave(data.text, updateTime) + debouncedSave(data.text) }, - [props.lastSaved, lastChanged] + [] ) useEffect(() => { const saveMarkdownNote = () => { const md = editorRef.current?.getMdValue() if (md) { - props.saveText(md, new Date()) + props.saveText(md) } } document.addEventListener('saveMarkdownNote', saveMarkdownNote) @@ -238,38 +219,6 @@ export function MarkdownNote(props: MarkdownNote): JSX.Element { renderHTML={(text: string) => mdParser.render(text)} onChange={handleEditorChange} /> - - {errorSaving && ( - - {errorSaving} - - )} - {props.lastSaved !== undefined ? ( - <> - {lastChanged === props.lastSaved - ? 'Saved' - : `Last saved ${formattedShortTime( - props.lastSaved.toISOString() - )}`} - - ) : null} - ) } diff --git a/packages/web/components/patterns/HighlightNotes.tsx b/packages/web/components/patterns/HighlightNotes.tsx index c8ba4809d..f2194ed5c 100644 --- a/packages/web/components/patterns/HighlightNotes.tsx +++ b/packages/web/components/patterns/HighlightNotes.tsx @@ -18,13 +18,6 @@ import throttle from 'lodash/throttle' import { updateHighlightMutation } from '../../lib/networking/mutations/updateHighlightMutation' import { Highlight } from '../../lib/networking/fragments/highlightFragment' import { Button } from '../elements/Button' -import { - ModalContent, - ModalOverlay, - ModalRoot, -} from '../elements/ModalPrimitives' -import { CloseButton } from '../elements/CloseButton' -import { StyledText } from '../elements/StyledText' import remarkGfm from 'remark-gfm' import { RcEditorStyles } from './RcEditorStyles' import { isDarkTheme } from '../../lib/themeUpdater' @@ -36,46 +29,6 @@ MdEditor.use(Plugins.TabInsert, { tabMapValue: 1, // note that 1 means a '\t' instead of ' '. }) -type NoteSectionProps = { - targetId: string - - placeHolder: string - mode: 'edit' | 'preview' - - 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 = { targetId: string @@ -92,9 +45,10 @@ type HighlightViewNoteProps = { export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element { const [lastSaved, setLastSaved] = useState(undefined) + const [errorSaving, setErrorSaving] = useState(undefined) const saveText = useCallback( - (text, updateTime) => { + (text, updateTime, interactive) => { ;(async () => { const success = await updateHighlightMutation({ annotation: text, @@ -104,13 +58,13 @@ export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element { setLastSaved(updateTime) props.highlight.annotation = text props.updateHighlight(props.highlight) - showSuccessToast('Note saved.', { - position: 'bottom-right', - }) + if (interactive) { + showSuccessToast('Note saved', { + position: 'bottom-right', + }) + } } else { - showErrorToast('Error saving note.', { - position: 'bottom-right', - }) + setErrorSaving('Error saving note.') } })() }, @@ -126,6 +80,7 @@ export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element { text={props.text} saveText={saveText} lastSaved={lastSaved} + errorSaving={errorSaving} fillBackground={true} /> ) @@ -143,14 +98,47 @@ type MarkdownNote = { fillBackground: boolean | undefined lastSaved: Date | undefined - saveText: (text: string, updateTime: Date) => void + errorSaving: string | undefined + + saveText: (text: string, updateTime: Date, interactive: boolean) => void } export function MarkdownNote(props: MarkdownNote): JSX.Element { const editorRef = useRef(null) - const [lastChanged, setLastChanged] = useState(undefined) - const [errorSaving, setErrorSaving] = useState(undefined) const isDark = isDarkTheme() + const [lastChanged, setLastChanged] = useState(undefined) + + const saveRef = useRef(props.saveText) + + useEffect(() => { + saveRef.current = props.saveText + }, [props]) + + const debouncedSave = useMemo< + (text: string, updateTime: Date) => void + >(() => { + const func = (text: string, updateTime: Date) => { + saveRef.current?.(text, updateTime, false) + } + 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) + }, + [] + ) return ( <> @@ -201,6 +189,7 @@ export function MarkdownNote(props: MarkdownNote): JSX.Element { height: '160px', }} renderHTML={(text: string) => mdParser.render(text)} + onChange={handleEditorChange} /> - {errorSaving && ( + {props.errorSaving && ( - {errorSaving} + {props.errorSaving} )} + {props.lastSaved !== undefined ? ( + <> + {lastChanged === props.lastSaved + ? 'Saved' + : `Last saved ${formattedShortTime( + props.lastSaved.toISOString() + )}`} + + ) : null} { const value = editorRef.current?.getMdValue() if (value) { - props.saveText(value, new Date()) + const updateTime = new Date() + setLastChanged(updateTime) + props.saveText(value, updateTime, true) props.setEditMode('preview') } else { showErrorToast('Error saving note.', { diff --git a/packages/web/components/patterns/RcEditorStyles.tsx b/packages/web/components/patterns/RcEditorStyles.tsx index 058cc6bbe..fcda55fa9 100644 --- a/packages/web/components/patterns/RcEditorStyles.tsx +++ b/packages/web/components/patterns/RcEditorStyles.tsx @@ -30,5 +30,11 @@ export const RcEditorStyles = (isDark: boolean, shadow: boolean) => { border: '1px solid $thBorderSubtle', backgroundColor: isDark ? '#2A2A2A' : 'white', }, + '.rc-md-editor:focus-within': { + outline: '2px solid $omnivoreCtaYellow', + borderRadius: '5px', + border: 'unset', + boxShadow: 'unset', + }, } } diff --git a/packages/web/components/templates/article/Notebook.tsx b/packages/web/components/templates/article/Notebook.tsx index 775fdcd11..bc13ba303 100644 --- a/packages/web/components/templates/article/Notebook.tsx +++ b/packages/web/components/templates/article/Notebook.tsx @@ -36,6 +36,8 @@ import { SetHighlightLabelsModalPresenter } from './SetLabelsModalPresenter' import { Button } from '../../elements/Button' import { ArticleNotes } from '../../patterns/ArticleNotes' import { useGetArticleQuery } from '../../../lib/networking/queries/useGetArticleQuery' +import { formattedShortTime } from '../../../lib/dateFormatting' +import { isDarkTheme } from '../../../lib/themeUpdater' type NotebookContentProps = { viewer: UserBasicData @@ -64,6 +66,8 @@ type NoteState = { } export function NotebookContent(props: NotebookContentProps): JSX.Element { + const isDark = isDarkTheme() + const { articleData, mutate } = useGetArticleQuery({ slug: props.item.slug, username: props.viewer.profile.username, @@ -74,9 +78,6 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element { const [labelsTarget, setLabelsTarget] = useState( undefined ) - const [notesEditMode, setNotesEditMode] = useState<'edit' | 'preview'>( - 'preview' - ) const noteState = useRef({ isCreating: false, note: undefined, @@ -87,14 +88,22 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element { return uuidv4() }, []) - const updateNote = useCallback((note: Highlight, text: string) => { - ;(async () => { - const result = await updateHighlightMutation({ - highlightId: note.id, - annotation: text, - }) - })() - }, []) + const updateNote = useCallback( + (note: Highlight, text: string, startTime: Date) => { + ;(async () => { + const result = await updateHighlightMutation({ + highlightId: note.id, + annotation: text, + }) + if (result) { + setLastSaved(startTime) + } else { + setErrorSaving('Error saving') + } + })() + }, + [] + ) const createNote = useCallback((text: string) => { console.log('creating note: ', newNoteId, noteState.current.isCreating) @@ -112,10 +121,13 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element { if (success) { noteState.current.note = success noteState.current.isCreating = false + } else { + setErrorSaving('Error creating note') } } catch (error) { console.error('error creating note: ', error) noteState.current.isCreating = false + setErrorSaving('Error creating note') } })() }, []) @@ -167,10 +179,12 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element { }, [highlights]) const handleSaveNoteText = useCallback( - (text, cb: (success: boolean) => void) => { - console.log('handleSaveNoteText', noteState.current) + (text) => { + const changeTime = new Date() + + setLastChanged(changeTime) if (noteState.current.note) { - updateNote(noteState.current.note, text) + updateNote(noteState.current.note, text, changeTime) return } if (noteState.current.isCreating) { @@ -195,6 +209,9 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element { const [articleNotesCollapsed, setArticleNotesCollapsed] = useState(false) const [highlightsCollapsed, setHighlightsCollapsed] = useState(false) + const [errorSaving, setErrorSaving] = useState(undefined) + const [lastChanged, setLastChanged] = useState(undefined) + const [lastSaved, setLastSaved] = useState(undefined) return ( @@ -212,20 +230,50 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element { setCollapsed={setArticleNotesCollapsed} /> {!articleNotesCollapsed && ( - - - + <> + + + + + {errorSaving && ( + + {errorSaving} + + )} + {lastSaved !== undefined ? ( + <> + {lastChanged === lastSaved + ? 'Saved' + : `Last saved ${formattedShortTime(lastSaved.toISOString())}`} + + ) : null} + + )} @@ -250,16 +298,14 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element { setShowConfirmDeleteHighlightId } updateHighlight={() => { - // dispatchAnnotations({ - // type: 'UPDATE_HIGHLIGHT', - // updateHighlight: highlight, - // }) + mutate() }} /> ))} {sortedHighlights.length === 0 && ( You have not added any highlights to this document.