diff --git a/web/components/challenges/create-challenge-modal.tsx b/web/components/challenges/create-challenge-modal.tsx index f18fbdad..ab657a87 100644 --- a/web/components/challenges/create-challenge-modal.tsx +++ b/web/components/challenges/create-challenge-modal.tsx @@ -43,7 +43,7 @@ export function CreateChallengeModal(props: { const { user, contract, isOpen, setOpen } = props const [challengeSlug, setChallengeSlug] = useState('') const [loading, setLoading] = useState(false) - const { editor } = useTextEditor({ placeholder: '' }) + const { editor } = useTextEditor({ key: 'challenge'}) return ( diff --git a/web/components/contract/contract-description.tsx b/web/components/contract/contract-description.tsx index 259e39df..79903ea3 100644 --- a/web/components/contract/contract-description.tsx +++ b/web/components/contract/contract-description.tsx @@ -46,6 +46,7 @@ function RichEditContract(props: { contract: Contract; isAdmin?: boolean }) { const [isSubmitting, setIsSubmitting] = useState(false) const { editor, upload } = useTextEditor({ + // key: `description ${contract.id}`, max: MAX_DESCRIPTION_LENGTH, defaultValue: contract.description, disabled: isSubmitting, diff --git a/web/components/create-post.tsx b/web/components/create-post.tsx index 6d42051c..28233b4d 100644 --- a/web/components/create-post.tsx +++ b/web/components/create-post.tsx @@ -21,6 +21,7 @@ export function CreatePost(props: { group?: Group }) { const { group } = props const { editor, upload } = useTextEditor({ + key: `post ${group?.id || ''}`, disabled: isSubmitting, }) diff --git a/web/components/editor.tsx b/web/components/editor.tsx index 813d492f..3491bb2a 100644 --- a/web/components/editor.tsx +++ b/web/components/editor.tsx @@ -13,7 +13,7 @@ import StarterKit from '@tiptap/starter-kit' import { Image } from '@tiptap/extension-image' import { Link } from '@tiptap/extension-link' import clsx from 'clsx' -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { Linkify } from './linkify' import { uploadImage } from 'web/lib/firebase/storage' import { useMutation } from 'react-query' @@ -42,6 +42,12 @@ import ItalicIcon from 'web/lib/icons/italic-icon' import LinkIcon from 'web/lib/icons/link-icon' import { getUrl } from 'common/util/parse' import { TiptapSpoiler } from 'common/util/tiptap-spoiler' +import { + storageStore, + usePersistentState, +} from 'web/hooks/use-persistent-state' +import { safeLocalStorage } from 'web/lib/util/local' +import { debounce } from 'lodash' const DisplayImage = Image.configure({ HTMLAttributes: { @@ -75,8 +81,20 @@ export function useTextEditor(props: { defaultValue?: Content disabled?: boolean simple?: boolean + key?: string // unique key for this text field for autosave }) { - const { placeholder, max, defaultValue = '', disabled, simple } = props + const { placeholder, max, defaultValue, disabled, simple, key } = props + + const [content, saveContent] = usePersistentState( + undefined, + { + key: `text ${key}`, + store: storageStore(safeLocalStorage()), + } + ) + + // eslint-disable-next-line react-hooks/exhaustive-deps + const save = useCallback(debounce(saveContent, 500), []) const editorClass = clsx( proseClass, @@ -88,6 +106,7 @@ export function useTextEditor(props: { const editor = useEditor({ editorProps: { attributes: { class: editorClass, spellcheck: 'false' } }, + onUpdate: key ? ({ editor }) => save(editor.getJSON()) : undefined, extensions: [ StarterKit.configure({ heading: simple ? false : { levels: [1, 2, 3] }, @@ -113,7 +132,7 @@ export function useTextEditor(props: { spoilerOpenClass: 'rounded-sm bg-greyscale-2', }), ], - content: defaultValue, + content: defaultValue ?? (key && content ? content : ''), }) const upload = useUploadMutation(editor) diff --git a/web/pages/create.tsx b/web/pages/create.tsx index e23ebf75..9ff9d8c9 100644 --- a/web/pages/create.tsx +++ b/web/pages/create.tsx @@ -23,7 +23,6 @@ import { ChoicesToggleGroup } from 'web/components/choices-toggle-group' import { getGroup, groupPath } from 'web/lib/firebase/groups' import { Group } from 'common/group' import { useTracking } from 'web/hooks/use-tracking' -import { useWarnUnsavedChanges } from 'web/hooks/use-warn-unsaved-changes' import { track } from 'web/lib/service/analytics' import { GroupSelector } from 'web/components/groups/group-selector' import { User } from 'common/user' @@ -228,6 +227,7 @@ export function NewContract(props: { : `e.g. I will choose the answer according to...` const { editor, upload } = useTextEditor({ + key: 'create market', max: MAX_DESCRIPTION_LENGTH, placeholder: descriptionPlaceholder, disabled: isSubmitting, @@ -236,9 +236,6 @@ export function NewContract(props: { : undefined, }) - const isEditorFilled = editor != null && !editor.isEmpty - useWarnUnsavedChanges(!isSubmitting && (Boolean(question) || isEditorFilled)) - function setCloseDateInDays(days: number) { const newCloseDate = dayjs().add(days, 'day').format('YYYY-MM-DD') setCloseDate(newCloseDate)