editor-autosave-fix
This commit is contained in:
parent
8f330ab914
commit
9b76a52430
|
@ -2,7 +2,7 @@ import { clsx } from 'clsx'
|
||||||
import deepEqual from 'fast-deep-equal'
|
import deepEqual from 'fast-deep-equal'
|
||||||
import { Accessor, Show, createMemo, createSignal, lazy, onCleanup, onMount } from 'solid-js'
|
import { Accessor, Show, createMemo, createSignal, lazy, onCleanup, onMount } from 'solid-js'
|
||||||
import { createStore } from 'solid-js/store'
|
import { createStore } from 'solid-js/store'
|
||||||
import { throttle } from 'throttle-debounce'
|
import { debounce } from 'throttle-debounce'
|
||||||
|
|
||||||
import { ShoutForm, useEditorContext } from '../../../context/editor'
|
import { ShoutForm, useEditorContext } from '../../../context/editor'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
@ -42,9 +42,8 @@ export const EMPTY_TOPIC: Topic = {
|
||||||
slug: '',
|
slug: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
const THROTTLING_INTERVAL = 2000
|
const AUTO_SAVE_DELAY = 3000
|
||||||
const AUTO_SAVE_INTERVAL = 5000
|
|
||||||
const AUTO_SAVE_DELAY = 5000
|
|
||||||
const handleScrollTopButtonClick = (e) => {
|
const handleScrollTopButtonClick = (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
window.scrollTo({
|
window.scrollTo({
|
||||||
|
@ -104,6 +103,8 @@ export const EditView = (props: Props) => {
|
||||||
return JSON.parse(form.media || '[]')
|
return JSON.parse(form.media || '[]')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const [hasChanges, setHasChanges] = createSignal(false)
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
setIsScrolled(window.scrollY > 0)
|
setIsScrolled(window.scrollY > 0)
|
||||||
|
@ -113,7 +114,7 @@ export const EditView = (props: Props) => {
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
window.removeEventListener('scroll', handleScroll)
|
window.removeEventListener('scroll', handleScroll)
|
||||||
})
|
})
|
||||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
|
||||||
const handleBeforeUnload = (event) => {
|
const handleBeforeUnload = (event) => {
|
||||||
if (!deepEqual(prevForm, form)) {
|
if (!deepEqual(prevForm, form)) {
|
||||||
event.returnValue = t(
|
event.returnValue = t(
|
||||||
|
@ -127,8 +128,8 @@ export const EditView = (props: Props) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleTitleInputChange = (value: string) => {
|
const handleTitleInputChange = (value: string) => {
|
||||||
setForm('title', value)
|
handleInputChange('title', value)
|
||||||
setForm('slug', slugify(value))
|
handleInputChange('slug', slugify(value))
|
||||||
if (value) {
|
if (value) {
|
||||||
setFormErrors('title', '')
|
setFormErrors('title', '')
|
||||||
}
|
}
|
||||||
|
@ -136,21 +137,21 @@ export const EditView = (props: Props) => {
|
||||||
|
|
||||||
const handleAddMedia = (data) => {
|
const handleAddMedia = (data) => {
|
||||||
const newMedia = [...mediaItems(), ...data]
|
const newMedia = [...mediaItems(), ...data]
|
||||||
setForm('media', JSON.stringify(newMedia))
|
handleInputChange('media', JSON.stringify(newMedia))
|
||||||
}
|
}
|
||||||
const handleSortedMedia = (data) => {
|
const handleSortedMedia = (data) => {
|
||||||
setForm('media', JSON.stringify(data))
|
handleInputChange('media', JSON.stringify(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMediaDelete = (index) => {
|
const handleMediaDelete = (index) => {
|
||||||
const copy = [...mediaItems()]
|
const copy = [...mediaItems()]
|
||||||
copy.splice(index, 1)
|
copy.splice(index, 1)
|
||||||
setForm('media', JSON.stringify(copy))
|
handleInputChange('media', JSON.stringify(copy))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMediaChange = (index, value) => {
|
const handleMediaChange = (index, value) => {
|
||||||
const updated = mediaItems().map((item, idx) => (idx === index ? value : item))
|
const updated = mediaItems().map((item, idx) => (idx === index ? value : item))
|
||||||
setForm('media', JSON.stringify(updated))
|
handleInputChange('media', JSON.stringify(updated))
|
||||||
}
|
}
|
||||||
|
|
||||||
const [baseAudioFields, setBaseAudioFields] = createSignal({
|
const [baseAudioFields, setBaseAudioFields] = createSignal({
|
||||||
|
@ -162,7 +163,7 @@ export const EditView = (props: Props) => {
|
||||||
const handleBaseFieldsChange = (key, value) => {
|
const handleBaseFieldsChange = (key, value) => {
|
||||||
if (mediaItems().length > 0) {
|
if (mediaItems().length > 0) {
|
||||||
const updated = mediaItems().map((media) => ({ ...media, [key]: value }))
|
const updated = mediaItems().map((media) => ({ ...media, [key]: value }))
|
||||||
setForm('media', JSON.stringify(updated))
|
handleInputChange('media', JSON.stringify(updated))
|
||||||
} else {
|
} else {
|
||||||
setBaseAudioFields({ ...baseAudioFields(), [key]: value })
|
setBaseAudioFields({ ...baseAudioFields(), [key]: value })
|
||||||
}
|
}
|
||||||
|
@ -182,34 +183,32 @@ export const EditView = (props: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let autoSaveTimeOutId: number | string | NodeJS.Timeout
|
|
||||||
|
|
||||||
const autoSave = async () => {
|
const autoSave = async () => {
|
||||||
const hasChanges = !deepEqual(form, prevForm)
|
console.log('autoSave called')
|
||||||
const hasTopic = Boolean(form.mainTopic)
|
if (hasChanges()) {
|
||||||
if (hasChanges || hasTopic) {
|
|
||||||
console.debug('saving draft', form)
|
console.debug('saving draft', form)
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
saveDraftToLocalStorage(form)
|
saveDraftToLocalStorage(form)
|
||||||
await saveDraft(form)
|
await saveDraft(form)
|
||||||
setPrevForm(clone(form))
|
setPrevForm(clone(form))
|
||||||
setTimeout(() => setSaving(false), AUTO_SAVE_DELAY)
|
setSaving(false)
|
||||||
|
setHasChanges(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throttle the autoSave function
|
const debouncedAutoSave = debounce(AUTO_SAVE_DELAY, autoSave)
|
||||||
const throttledAutoSave = throttle(THROTTLING_INTERVAL, autoSave)
|
|
||||||
|
|
||||||
const autoSaveRecursive = () => {
|
const handleInputChange = (key, value) => {
|
||||||
autoSaveTimeOutId = setTimeout(() => {
|
console.log(`[handleInputChange] ${key}: ${value}`)
|
||||||
throttledAutoSave()
|
setForm(key, value)
|
||||||
autoSaveRecursive()
|
setHasChanges(true)
|
||||||
}, AUTO_SAVE_INTERVAL)
|
debouncedAutoSave()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
autoSaveRecursive()
|
onCleanup(() => {
|
||||||
onCleanup(() => clearTimeout(autoSaveTimeOutId))
|
debouncedAutoSave.cancel()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const showSubtitleInput = () => {
|
const showSubtitleInput = () => {
|
||||||
|
@ -310,7 +309,7 @@ export const EditView = (props: Props) => {
|
||||||
subtitleInput.current = el
|
subtitleInput.current = el
|
||||||
}}
|
}}
|
||||||
allowEnterKey={false}
|
allowEnterKey={false}
|
||||||
value={(value) => setForm('subtitle', value || '')}
|
value={(value) => handleInputChange('subtitle', value || '')}
|
||||||
class={styles.subtitleInput}
|
class={styles.subtitleInput}
|
||||||
placeholder={t('Subheader')}
|
placeholder={t('Subheader')}
|
||||||
initialValue={form.subtitle || ''}
|
initialValue={form.subtitle || ''}
|
||||||
|
@ -324,7 +323,7 @@ export const EditView = (props: Props) => {
|
||||||
smallHeight={true}
|
smallHeight={true}
|
||||||
placeholder={t('A short introduction to keep the reader interested')}
|
placeholder={t('A short introduction to keep the reader interested')}
|
||||||
initialContent={form.lead}
|
initialContent={form.lead}
|
||||||
onChange={(value) => setForm('lead', value)}
|
onChange={(value) => handleInputChange('lead', value)}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
</Show>
|
</Show>
|
||||||
|
@ -345,7 +344,7 @@ export const EditView = (props: Props) => {
|
||||||
}
|
}
|
||||||
isMultiply={false}
|
isMultiply={false}
|
||||||
fileType={'image'}
|
fileType={'image'}
|
||||||
onUpload={(val) => setForm('coverImageUrl', val[0].url)}
|
onUpload={(val) => handleInputChange('coverImageUrl', val[0].url)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -362,7 +361,7 @@ export const EditView = (props: Props) => {
|
||||||
<div
|
<div
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
class={styles.delete}
|
class={styles.delete}
|
||||||
onClick={() => setForm('coverImageUrl', null)}
|
onClick={() => handleInputChange('coverImageUrl', null)}
|
||||||
>
|
>
|
||||||
<Icon name="close-white" />
|
<Icon name="close-white" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -408,7 +407,7 @@ export const EditView = (props: Props) => {
|
||||||
<Editor
|
<Editor
|
||||||
shoutId={form.shoutId}
|
shoutId={form.shoutId}
|
||||||
initialContent={form.body}
|
initialContent={form.body}
|
||||||
onChange={(body) => setForm('body', body)}
|
onChange={(body) => handleInputChange('body', body)}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user