Init AutoSave (#154)
* Init AutoSave * Saving Notice * Hide save button * Hide save button * Fix redirect * resolve Conversation
This commit is contained in:
parent
8086a54d81
commit
039b60f022
7
package-lock.json
generated
7
package-lock.json
generated
|
@ -10,6 +10,7 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@hocuspocus/provider": "2.0.6",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"form-data": "4.0.0",
|
||||
"i18next": "22.4.15",
|
||||
"mailgun.js": "8.2.1",
|
||||
|
@ -8849,8 +8850,7 @@
|
|||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.2.12",
|
||||
|
@ -25813,8 +25813,7 @@
|
|||
"fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"fast-glob": {
|
||||
"version": "3.2.12",
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@hocuspocus/provider": "2.0.6",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"form-data": "4.0.0",
|
||||
"i18next": "22.4.15",
|
||||
"mailgun.js": "8.2.1",
|
||||
|
|
|
@ -72,7 +72,6 @@
|
|||
"Create gallery": "Create gallery",
|
||||
"Create post": "Create post",
|
||||
"Create video": "Create video",
|
||||
"contents": "contents",
|
||||
"Date of Birth": "Date of Birth",
|
||||
"Decline": "Decline",
|
||||
"Delete": "Delete",
|
||||
|
@ -237,6 +236,7 @@
|
|||
"Restore password": "Restore password",
|
||||
"Save draft": "Save draft",
|
||||
"Save settings": "Save settings",
|
||||
"Saving...": "Saving...",
|
||||
"Scroll up": "Scroll up",
|
||||
"Search": "Search",
|
||||
"Search author": "Search author",
|
||||
|
@ -340,6 +340,7 @@
|
|||
"cancel": "cancel",
|
||||
"collections": "collections",
|
||||
"community": "community",
|
||||
"contents": "contents",
|
||||
"delimiter": "delimiter",
|
||||
"discussion": "discourse",
|
||||
"drafts": "drafts",
|
||||
|
|
|
@ -76,7 +76,6 @@
|
|||
"Create gallery": "Создать галерею",
|
||||
"Create post": "Создать публикацию",
|
||||
"Create video": "Создать видео",
|
||||
"contents": "оглавление",
|
||||
"Date of Birth": "Дата рождения",
|
||||
"Decline": "Отмена",
|
||||
"Delete": "Удалить",
|
||||
|
@ -253,6 +252,7 @@
|
|||
"Save": "Сохранить",
|
||||
"Save draft": "Сохранить черновик",
|
||||
"Save settings": "Сохранить настройки",
|
||||
"Saving...": "Сохраняем...",
|
||||
"Scroll up": "Наверх",
|
||||
"Search": "Поиск",
|
||||
"Search author": "Поиск автора",
|
||||
|
@ -358,6 +358,7 @@
|
|||
"cancel": "отменить",
|
||||
"collections": "коллекции",
|
||||
"community": "сообщество",
|
||||
"contents": "оглавление",
|
||||
"create_chat": "Создать чат",
|
||||
"create_group": "Создать группу",
|
||||
"delimiter": "разделитель",
|
||||
|
|
|
@ -25,20 +25,20 @@
|
|||
.actions {
|
||||
@include font-size(1.2rem);
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
||||
.actionItem {
|
||||
border: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
cursor: pointer;
|
||||
|
||||
a + a {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.deleteLink {
|
||||
&.delete {
|
||||
color: #d00820;
|
||||
}
|
||||
|
||||
.publishLink {
|
||||
&.publish {
|
||||
color: #2bb452;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,13 +53,18 @@ export const Draft = (props: Props) => {
|
|||
<span class={styles.title}>{props.shout.title || t('Unnamed draft')}</span> {props.shout.subtitle}
|
||||
</div>
|
||||
<div class={styles.actions}>
|
||||
<a href={getPagePath(router, 'edit', { shoutId: props.shout.id.toString() })}>{t('Edit')}</a>
|
||||
<a href="#" onClick={handlePublishLinkClick} class={styles.publishLink}>
|
||||
<a
|
||||
class={styles.actionItem}
|
||||
href={getPagePath(router, 'edit', { shoutId: props.shout.id.toString() })}
|
||||
>
|
||||
{t('Edit')}
|
||||
</a>
|
||||
<span onClick={handlePublishLinkClick} class={clsx(styles.actionItem, styles.publish)}>
|
||||
{t('Publish')}
|
||||
</a>
|
||||
<a href="#" onClick={handleDeleteLinkClick} class={styles.deleteLink}>
|
||||
</span>
|
||||
<span onClick={handleDeleteLinkClick} class={clsx(styles.actionItem, styles.delete)}>
|
||||
{t('Delete')}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
.AutoSaveNotice {
|
||||
@include font-size(1.4rem);
|
||||
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
position: sticky;
|
||||
top: calc(100vh - 40px);
|
||||
margin-left: auto;
|
||||
padding: 2px 6px;
|
||||
border-radius: 2px;
|
||||
z-index: 2;
|
||||
font-weight: 500;
|
||||
transition: 0.6s ease-in-out;
|
||||
background: rgba(white, 0.3);
|
||||
backdrop-filter: blur(4px);
|
||||
border: 1px solid var(--secondary-color);
|
||||
left: 100%;
|
||||
opacity: 0;
|
||||
right: -14rem;
|
||||
pointer-events: none;
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 0.65;
|
||||
right: 2rem;
|
||||
}
|
||||
}
|
20
src/components/Editor/AutoSaveNotice/AutoSaveNotice.tsx
Normal file
20
src/components/Editor/AutoSaveNotice/AutoSaveNotice.tsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { clsx } from 'clsx'
|
||||
import styles from './AutoSaveNotice.module.scss'
|
||||
import { Loading } from '../../_shared/Loading'
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
|
||||
type Props = {
|
||||
active: boolean
|
||||
}
|
||||
|
||||
export const AutoSaveNotice = (props: Props) => {
|
||||
const { t } = useLocalize()
|
||||
return (
|
||||
<div class={clsx(styles.AutoSaveNotice, { [styles.active]: props.active })}>
|
||||
<div class={styles.icon}>
|
||||
<Loading size="tiny" />
|
||||
</div>
|
||||
<div>{t('Saving...')}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
1
src/components/Editor/AutoSaveNotice/index.ts
Normal file
1
src/components/Editor/AutoSaveNotice/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { AutoSaveNotice } from './AutoSaveNotice'
|
|
@ -1,6 +1,5 @@
|
|||
import { createEffect, createSignal, Show } from 'solid-js'
|
||||
import { createTiptapEditor, useEditorHTML } from 'solid-tiptap'
|
||||
import { IndexeddbPersistence } from 'y-indexeddb'
|
||||
import uniqolor from 'uniqolor'
|
||||
import * as Y from 'yjs'
|
||||
import type { Doc } from 'yjs/dist/src/utils/Doc'
|
||||
|
@ -58,7 +57,6 @@ type Props = {
|
|||
}
|
||||
|
||||
const yDocs: Record<string, Doc> = {}
|
||||
const persisters: Record<string, IndexeddbPersistence> = {}
|
||||
const providers: Record<string, HocuspocusProvider> = {}
|
||||
|
||||
export const Editor = (props: Props) => {
|
||||
|
@ -80,10 +78,6 @@ export const Editor = (props: Props) => {
|
|||
})
|
||||
}
|
||||
|
||||
if (!persisters[docName]) {
|
||||
persisters[docName] = new IndexeddbPersistence(docName, yDocs[docName])
|
||||
}
|
||||
|
||||
const editorElRef: {
|
||||
current: HTMLDivElement
|
||||
} = {
|
||||
|
|
|
@ -25,6 +25,7 @@ export const Panel = (props: Props) => {
|
|||
isEditorPanelVisible,
|
||||
wordCounter,
|
||||
editorRef,
|
||||
form,
|
||||
actions: { toggleEditorPanel, saveShout, publishShout }
|
||||
} = useEditorContext()
|
||||
|
||||
|
@ -48,11 +49,11 @@ export const Panel = (props: Props) => {
|
|||
})
|
||||
|
||||
const handleSaveClick = () => {
|
||||
saveShout()
|
||||
saveShout(form)
|
||||
}
|
||||
|
||||
const handlePublishClick = () => {
|
||||
publishShout()
|
||||
publishShout(form)
|
||||
}
|
||||
|
||||
const handleFixTypographyClick = () => {
|
||||
|
|
|
@ -15,17 +15,18 @@ import { Button } from '../_shared/Button'
|
|||
import { useEditorContext } from '../../context/editor'
|
||||
import { Popover } from '../_shared/Popover'
|
||||
|
||||
type HeaderAuthProps = {
|
||||
type Props = {
|
||||
setIsProfilePopupVisible: (value: boolean) => void
|
||||
}
|
||||
type IconedButton = {
|
||||
|
||||
type IconedButtonProps = {
|
||||
value: string
|
||||
icon: string
|
||||
action: () => void
|
||||
}
|
||||
|
||||
const MD_WIDTH_BREAKPOINT = 992
|
||||
export const HeaderAuth = (props: HeaderAuthProps) => {
|
||||
export const HeaderAuth = (props: Props) => {
|
||||
const { t } = useLocalize()
|
||||
const { page } = useRouter()
|
||||
const [visibleWarnings, setVisibleWarnings] = createSignal(false)
|
||||
|
@ -34,6 +35,7 @@ export const HeaderAuth = (props: HeaderAuthProps) => {
|
|||
const { session, isSessionLoaded, isAuthenticated } = useSession()
|
||||
|
||||
const {
|
||||
form,
|
||||
actions: { toggleEditorPanel, saveShout, publishShout }
|
||||
} = useEditorContext()
|
||||
|
||||
|
@ -61,11 +63,11 @@ export const HeaderAuth = (props: HeaderAuthProps) => {
|
|||
}
|
||||
|
||||
const handleSaveButtonClick = () => {
|
||||
saveShout()
|
||||
saveShout(form)
|
||||
}
|
||||
|
||||
const handlePublishButtonClick = () => {
|
||||
publishShout()
|
||||
publishShout(form)
|
||||
}
|
||||
|
||||
const [width, setWidth] = createSignal(0)
|
||||
|
@ -76,25 +78,25 @@ export const HeaderAuth = (props: HeaderAuthProps) => {
|
|||
onCleanup(() => window.removeEventListener('resize', handleResize))
|
||||
})
|
||||
|
||||
const renderIconedButton = (iconedButtonProps: IconedButton) => {
|
||||
const renderIconedButton = (buttonProps: IconedButtonProps) => {
|
||||
return (
|
||||
<Show
|
||||
when={width() < MD_WIDTH_BREAKPOINT}
|
||||
fallback={
|
||||
<Button
|
||||
value={<span class={styles.textLabel}>{iconedButtonProps.value}</span>}
|
||||
value={<span class={styles.textLabel}>{buttonProps.value}</span>}
|
||||
variant={'outline'}
|
||||
onClick={handleSaveButtonClick}
|
||||
onClick={buttonProps.action}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Popover content={iconedButtonProps.value}>
|
||||
<Popover content={buttonProps.value}>
|
||||
{(ref) => (
|
||||
<Button
|
||||
ref={ref}
|
||||
variant={'outline'}
|
||||
onClick={handleSaveButtonClick}
|
||||
value={<Icon name={iconedButtonProps.icon} class={styles.icon} />}
|
||||
onClick={buttonProps.action}
|
||||
value={<Icon name={buttonProps.icon} class={styles.icon} />}
|
||||
/>
|
||||
)}
|
||||
</Popover>
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Title } from '@solidjs/meta'
|
|||
import type { Shout, Topic } from '../../graphql/types.gen'
|
||||
import { apiClient } from '../../utils/apiClient'
|
||||
import { useRouter } from '../../stores/router'
|
||||
import { useEditorContext } from '../../context/editor'
|
||||
import { ShoutForm, useEditorContext } from '../../context/editor'
|
||||
import { Editor, Panel, TopicSelect, UploadModalContent } from '../Editor'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
import { Button } from '../_shared/Button'
|
||||
|
@ -21,11 +21,14 @@ import { slugify } from '../../utils/slugify'
|
|||
import { SolidSwiper } from '../_shared/SolidSwiper'
|
||||
import { DropArea } from '../_shared/DropArea'
|
||||
import { LayoutType, MediaItem } from '../../pages/types'
|
||||
import { clone } from '../../utils/clone'
|
||||
import deepEqual from 'fast-deep-equal'
|
||||
import { AutoSaveNotice } from '../Editor/AutoSaveNotice'
|
||||
|
||||
type Props = {
|
||||
shout: Shout
|
||||
}
|
||||
|
||||
const AUTO_SAVE_INTERVAL = 5000
|
||||
const handleScrollTopButtonClick = (e) => {
|
||||
e.preventDefault()
|
||||
window.scrollTo({
|
||||
|
@ -47,17 +50,22 @@ export const EditView = (props: Props) => {
|
|||
const [coverImage, setCoverImage] = createSignal<string>(null)
|
||||
|
||||
const { page } = useRouter()
|
||||
|
||||
const {
|
||||
form,
|
||||
formErrors,
|
||||
actions: { setForm, setFormErrors }
|
||||
actions: { setForm, setFormErrors, saveDraft, saveDraftToLocalStorage, getDraftFromLocalStorage }
|
||||
} = useEditorContext()
|
||||
|
||||
const shoutTopics = props.shout.topics || []
|
||||
|
||||
const draft = getDraftFromLocalStorage(props.shout.id)
|
||||
if (draft) {
|
||||
setForm(draft)
|
||||
} else {
|
||||
setForm({
|
||||
shoutId: props.shout.id,
|
||||
slug: props.shout.slug,
|
||||
shoutId: props.shout.id,
|
||||
title: props.shout.title,
|
||||
subtitle: props.shout.subtitle,
|
||||
selectedTopics: shoutTopics,
|
||||
|
@ -67,6 +75,10 @@ export const EditView = (props: Props) => {
|
|||
media: props.shout.media,
|
||||
layout: props.shout.layout
|
||||
})
|
||||
}
|
||||
|
||||
const [prevForm, setPrevForm] = createSignal<ShoutForm>(clone(form))
|
||||
const [saving, setSaving] = createSignal(false)
|
||||
|
||||
const mediaItems: Accessor<MediaItem[]> = createMemo(() => {
|
||||
return JSON.parse(form.media || '[]')
|
||||
|
@ -195,12 +207,46 @@ export const EditView = (props: Props) => {
|
|||
}
|
||||
}
|
||||
|
||||
let autoSaveTimeOutId
|
||||
|
||||
const autoSaveRecursive = () => {
|
||||
autoSaveTimeOutId = setTimeout(async () => {
|
||||
const hasChanges = !deepEqual(form, prevForm())
|
||||
if (hasChanges) {
|
||||
setSaving(true)
|
||||
if (props.shout.visibility === 'owner') {
|
||||
await saveDraft(form)
|
||||
} else {
|
||||
saveDraftToLocalStorage(form)
|
||||
}
|
||||
setPrevForm(clone(form))
|
||||
setTimeout(() => {
|
||||
setSaving(false)
|
||||
}, 2000)
|
||||
}
|
||||
autoSaveRecursive()
|
||||
}, AUTO_SAVE_INTERVAL)
|
||||
}
|
||||
|
||||
const stopAutoSave = () => {
|
||||
clearTimeout(autoSaveTimeOutId)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
autoSaveRecursive()
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
stopAutoSave()
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class={styles.container}>
|
||||
<Title>{pageTitle()}</Title>
|
||||
<form>
|
||||
<div class="wide-container">
|
||||
<AutoSaveNotice active={saving()} />
|
||||
<button
|
||||
class={clsx(styles.scrollTopButton, {
|
||||
[styles.visible]: isScrolled()
|
||||
|
|
|
@ -29,4 +29,8 @@
|
|||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
.tiny & {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,14 @@ import styles from './Loading.module.scss'
|
|||
import { clsx } from 'clsx'
|
||||
|
||||
type Props = {
|
||||
size?: 'small'
|
||||
size?: 'small' | 'tiny'
|
||||
}
|
||||
export const Loading = (props: Props) => {
|
||||
return (
|
||||
<div
|
||||
class={clsx(styles.container, {
|
||||
[styles.small]: props.size === 'small'
|
||||
[styles.small]: props.size === 'small',
|
||||
[styles.tiny]: props.size === 'tiny'
|
||||
})}
|
||||
>
|
||||
<div class={styles.icon} />
|
||||
|
|
|
@ -15,7 +15,7 @@ type WordCounter = {
|
|||
words: number
|
||||
}
|
||||
|
||||
type ShoutForm = {
|
||||
export type ShoutForm = {
|
||||
layout?: string
|
||||
shoutId: number
|
||||
slug: string
|
||||
|
@ -35,8 +35,11 @@ type EditorContextType = {
|
|||
formErrors: Record<keyof ShoutForm, string>
|
||||
editorRef: { current: () => Editor }
|
||||
actions: {
|
||||
saveShout: () => Promise<void>
|
||||
publishShout: () => Promise<void>
|
||||
saveShout: (form: ShoutForm) => Promise<void>
|
||||
saveDraft: (form: ShoutForm) => Promise<void>
|
||||
saveDraftToLocalStorage: (form: ShoutForm) => void
|
||||
getDraftFromLocalStorage: (shoutId: number) => ShoutForm
|
||||
publishShout: (form: ShoutForm) => Promise<void>
|
||||
publishShoutById: (shoutId: number) => Promise<void>
|
||||
deleteShout: (shoutId: number) => Promise<boolean>
|
||||
toggleEditorPanel: () => void
|
||||
|
@ -109,26 +112,26 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
|||
return true
|
||||
}
|
||||
|
||||
const updateShout = async ({ publish }: { publish: boolean }) => {
|
||||
const updateShout = async (formToUpdate: ShoutForm, { publish }: { publish: boolean }) => {
|
||||
return apiClient.updateArticle({
|
||||
shoutId: form.shoutId,
|
||||
shoutId: formToUpdate.shoutId,
|
||||
shoutInput: {
|
||||
body: form.body,
|
||||
topics: form.selectedTopics.map((topic) => topic2topicInput(topic)),
|
||||
body: formToUpdate.body,
|
||||
topics: formToUpdate.selectedTopics.map((topic) => topic2topicInput(topic)),
|
||||
// authors?: InputMaybe<Array<InputMaybe<Scalars['String']>>>
|
||||
// community?: InputMaybe<Scalars['Int']>
|
||||
mainTopic: topic2topicInput(form.mainTopic),
|
||||
slug: form.slug,
|
||||
subtitle: form.subtitle,
|
||||
title: form.title,
|
||||
cover: form.coverImageUrl,
|
||||
media: form.media
|
||||
mainTopic: topic2topicInput(formToUpdate.mainTopic),
|
||||
slug: formToUpdate.slug,
|
||||
subtitle: formToUpdate.subtitle,
|
||||
title: formToUpdate.title,
|
||||
cover: formToUpdate.coverImageUrl,
|
||||
media: formToUpdate.media
|
||||
},
|
||||
publish
|
||||
})
|
||||
}
|
||||
|
||||
const saveShout = async () => {
|
||||
const saveShout = async (formToSave: ShoutForm) => {
|
||||
if (isEditorPanelVisible()) {
|
||||
toggleEditorPanel()
|
||||
}
|
||||
|
@ -142,7 +145,8 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
|||
}
|
||||
|
||||
try {
|
||||
const shout = await updateShout({ publish: false })
|
||||
const shout = await updateShout(formToSave, { publish: false })
|
||||
removeDraftFromLocalStorage(formToSave.shoutId)
|
||||
|
||||
if (shout.visibility === 'owner') {
|
||||
openPage(router, 'drafts')
|
||||
|
@ -155,7 +159,22 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
|||
}
|
||||
}
|
||||
|
||||
const publishShout = async () => {
|
||||
const saveDraft = async (draftForm: ShoutForm) => {
|
||||
await updateShout(draftForm, { publish: false })
|
||||
}
|
||||
|
||||
const saveDraftToLocalStorage = (formToSave: ShoutForm) => {
|
||||
localStorage.setItem(`shout-${formToSave.shoutId}`, JSON.stringify(formToSave))
|
||||
}
|
||||
const getDraftFromLocalStorage = (shoutId: number) => {
|
||||
return JSON.parse(localStorage.getItem(`shout-${shoutId}`))
|
||||
}
|
||||
|
||||
const removeDraftFromLocalStorage = (shoutId: number) => {
|
||||
localStorage.removeItem(`shout-${shoutId}`)
|
||||
}
|
||||
|
||||
const publishShout = async (formToPublish: ShoutForm) => {
|
||||
if (isEditorPanelVisible()) {
|
||||
toggleEditorPanel()
|
||||
}
|
||||
|
@ -165,7 +184,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
|||
return
|
||||
}
|
||||
|
||||
await updateShout({ publish: false })
|
||||
await updateShout(formToPublish, { publish: false })
|
||||
|
||||
const slug = slugify(form.title)
|
||||
setForm('slug', slug)
|
||||
|
@ -178,7 +197,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
|||
}
|
||||
|
||||
try {
|
||||
await updateShout({ publish: true })
|
||||
await updateShout(formToPublish, { publish: true })
|
||||
openPage(router, 'feed')
|
||||
} catch (error) {
|
||||
console.error('[publishShout]', error)
|
||||
|
@ -218,6 +237,9 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
|||
|
||||
const actions = {
|
||||
saveShout,
|
||||
saveDraft,
|
||||
saveDraftToLocalStorage,
|
||||
getDraftFromLocalStorage,
|
||||
publishShout,
|
||||
publishShoutById,
|
||||
deleteShout,
|
||||
|
|
|
@ -5,6 +5,7 @@ export default gql`
|
|||
loadShout(slug: $slug, shout_id: $shoutId) {
|
||||
id
|
||||
title
|
||||
visibility
|
||||
subtitle
|
||||
slug
|
||||
layout
|
||||
|
|
Loading…
Reference in New Issue
Block a user