webapp/src/context/editor.tsx

202 lines
4.9 KiB
TypeScript
Raw Normal View History

import type { JSX } from 'solid-js'
import { Accessor, createContext, createSignal, useContext } from 'solid-js'
import { createStore, SetStoreFunction } from 'solid-js/store'
import { Topic } from '../graphql/types.gen'
2023-05-05 20:05:50 +00:00
import { apiClient } from '../utils/apiClient'
import { useLocalize } from './localize'
import { useSnackbar } from './snackbar'
2023-05-08 17:21:06 +00:00
import { openPage } from '@nanostores/router'
import { router, useRouter } from '../stores/router'
2023-05-10 20:20:53 +00:00
import { slugify } from '../utils/slugify'
type WordCounter = {
characters: number
words: number
}
type ShoutForm = {
shoutId: number
slug: string
title: string
subtitle: string
selectedTopics: Topic[]
2023-05-10 20:20:53 +00:00
mainTopic?: Topic
body: string
coverImageUrl: string
}
type EditorContextType = {
isEditorPanelVisible: Accessor<boolean>
wordCounter: Accessor<WordCounter>
form: ShoutForm
2023-05-10 20:20:53 +00:00
formErrors: Record<keyof ShoutForm, string>
actions: {
2023-05-08 17:21:06 +00:00
saveShout: () => Promise<void>
publishShout: () => Promise<void>
publishShoutById: (shoutId: number) => Promise<void>
deleteShout: (shoutId: number) => Promise<boolean>
toggleEditorPanel: () => void
countWords: (value: WordCounter) => void
setForm: SetStoreFunction<ShoutForm>
2023-05-10 20:20:53 +00:00
setFormErrors: SetStoreFunction<Record<keyof ShoutForm, string>>
}
}
const EditorContext = createContext<EditorContextType>()
export function useEditorContext() {
return useContext(EditorContext)
}
export const EditorProvider = (props: { children: JSX.Element }) => {
2023-05-05 20:05:50 +00:00
const { t } = useLocalize()
2023-05-08 17:21:06 +00:00
const { page } = useRouter()
2023-05-05 20:05:50 +00:00
const {
actions: { showSnackbar }
} = useSnackbar()
2023-05-03 16:13:48 +00:00
const [isEditorPanelVisible, setIsEditorPanelVisible] = createSignal<boolean>(false)
const [form, setForm] = createStore<ShoutForm>(null)
2023-05-10 20:20:53 +00:00
const [formErrors, setFormErrors] = createStore<Record<keyof ShoutForm, string>>(null)
const [wordCounter, setWordCounter] = createSignal<WordCounter>({
characters: 0,
words: 0
})
2023-05-05 20:05:50 +00:00
2023-05-03 16:13:48 +00:00
const toggleEditorPanel = () => setIsEditorPanelVisible((value) => !value)
const countWords = (value) => setWordCounter(value)
2023-05-05 20:05:50 +00:00
2023-05-08 17:21:06 +00:00
const validate = () => {
2023-05-05 20:05:50 +00:00
if (!form.title) {
setFormErrors('title', t('Required'))
return false
}
2023-05-08 17:21:06 +00:00
return true
}
2023-05-10 20:20:53 +00:00
const validateSettings = () => {
if (form.selectedTopics.length === 0) {
setFormErrors('selectedTopics', t('Required'))
return false
}
return true
}
const updateShout = async ({ publish }: { publish: boolean }) => {
return apiClient.updateArticle({
shoutId: form.shoutId,
shoutInput: {
body: form.body,
topics: form.selectedTopics,
// authors?: InputMaybe<Array<InputMaybe<Scalars['String']>>>
// community?: InputMaybe<Scalars['Int']>
mainTopic: form.mainTopic,
slug: form.slug,
subtitle: form.subtitle,
title: form.title,
cover: form.coverImageUrl
},
publish
})
}
2023-05-08 17:21:06 +00:00
const saveShout = async () => {
if (isEditorPanelVisible()) {
toggleEditorPanel()
}
2023-05-10 20:20:53 +00:00
if (page().route === 'edit' && !validate()) {
return
}
if (page().route === 'editSettings' && !validateSettings()) {
2023-05-08 17:21:06 +00:00
return
}
2023-05-05 20:05:50 +00:00
try {
2023-05-10 20:20:53 +00:00
const shout = await updateShout({ publish: false })
2023-05-08 17:21:06 +00:00
if (shout.visibility === 'owner') {
openPage(router, 'drafts')
} else {
openPage(router, 'article', { slug: shout.slug })
}
2023-05-05 20:05:50 +00:00
} catch (error) {
2023-05-08 18:01:11 +00:00
console.error('[saveShout]', error)
2023-05-05 20:05:50 +00:00
showSnackbar({ type: 'error', body: t('Error') })
}
}
const publishShout = async () => {
if (isEditorPanelVisible()) {
toggleEditorPanel()
}
2023-05-10 20:20:53 +00:00
2023-05-08 17:21:06 +00:00
if (!validate()) {
return
}
2023-05-10 20:20:53 +00:00
2023-05-08 17:21:06 +00:00
if (page().route === 'edit') {
2023-05-10 20:20:53 +00:00
const slug = slugify(form.title)
2023-05-08 17:21:06 +00:00
setForm('slug', slug)
openPage(router, 'editSettings', { shoutId: form.shoutId.toString() })
return
}
2023-05-05 20:05:50 +00:00
try {
2023-05-10 20:20:53 +00:00
await updateShout({ publish: true })
2023-05-08 18:26:42 +00:00
openPage(router, 'feed')
2023-05-08 17:21:06 +00:00
} catch (error) {
2023-05-08 18:01:11 +00:00
console.error('[publishShout]', error)
2023-05-08 17:21:06 +00:00
showSnackbar({ type: 'error', body: t('Error') })
}
}
const publishShoutById = async (shoutId: number) => {
try {
await apiClient.updateArticle({
shoutId,
publish: true
})
openPage(router, 'feed')
} catch (error) {
2023-05-08 18:01:11 +00:00
console.error('[publishShoutById]', error)
2023-05-08 17:21:06 +00:00
showSnackbar({ type: 'error', body: t('Error') })
}
}
const deleteShout = async (shoutId: number) => {
try {
await apiClient.deleteShout({
shoutId
2023-05-05 20:05:50 +00:00
})
return true
} catch {
showSnackbar({ type: 'error', body: t('Error') })
return false
}
}
const actions = {
2023-05-05 20:05:50 +00:00
saveShout,
publishShout,
2023-05-08 17:21:06 +00:00
publishShoutById,
deleteShout,
toggleEditorPanel,
countWords,
2023-05-05 20:05:50 +00:00
setForm,
setFormErrors
}
2023-05-05 20:05:50 +00:00
const value: EditorContextType = { actions, form, formErrors, isEditorPanelVisible, wordCounter }
return <EditorContext.Provider value={value}>{props.children}</EditorContext.Provider>
}