webapp/src/components/Views/Edit.tsx

288 lines
11 KiB
TypeScript
Raw Normal View History

2023-05-07 19:33:20 +00:00
import { createSignal, onCleanup, onMount, Show } from 'solid-js'
2023-02-17 09:21:02 +00:00
import { useLocalize } from '../../context/localize'
import { clsx } from 'clsx'
2023-03-13 12:26:25 +00:00
import { Title } from '@solidjs/meta'
2023-04-11 13:57:48 +00:00
import type { Shout, Topic } from '../../graphql/types.gen'
2023-03-23 17:15:50 +00:00
import { apiClient } from '../../utils/apiClient'
import { useRouter } from '../../stores/router'
import { useEditorContext } from '../../context/editor'
2023-05-09 04:58:00 +00:00
import { Editor, Panel, TopicSelect, UploadModalContent } from '../Editor'
2023-05-06 15:04:50 +00:00
import { Icon } from '../_shared/Icon'
2023-05-08 18:23:51 +00:00
import { Button } from '../_shared/Button'
import styles from './Edit.module.scss'
import { useSession } from '../../context/session'
2023-05-09 04:58:00 +00:00
import { Modal } from '../Nav/Modal'
import { hideModal, showModal } from '../../stores/ui'
import { imageProxy } from '../../utils/imageProxy'
2023-05-11 11:33:01 +00:00
import { GrowingTextarea } from '../_shared/GrowingTextarea'
2023-03-23 17:15:50 +00:00
2023-04-11 13:57:48 +00:00
type EditViewProps = {
shout: Shout
}
2023-05-09 05:05:06 +00:00
const scrollTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
})
}
2023-05-10 20:20:53 +00:00
const EMPTY_TOPIC: Topic = {
id: -1,
slug: ''
}
2023-04-11 13:57:48 +00:00
export const EditView = (props: EditViewProps) => {
2023-02-17 09:21:02 +00:00
const { t } = useLocalize()
2023-05-08 18:23:51 +00:00
const { user } = useSession()
2023-05-07 19:33:20 +00:00
const [isScrolled, setIsScrolled] = createSignal(false)
2023-03-23 17:15:50 +00:00
const [topics, setTopics] = createSignal<Topic[]>(null)
2023-05-09 04:58:00 +00:00
const [coverImage, setCoverImage] = createSignal<string>(null)
2023-03-26 18:31:34 +00:00
const { page } = useRouter()
const {
form,
2023-05-05 20:05:50 +00:00
formErrors,
actions: { setForm, setFormErrors }
} = useEditorContext()
2023-03-23 17:15:50 +00:00
2023-05-10 20:20:53 +00:00
const shoutTopics = props.shout.topics || []
setForm({
shoutId: props.shout.id,
2023-04-11 13:57:48 +00:00
slug: props.shout.slug,
title: props.shout.title,
subtitle: props.shout.subtitle,
2023-05-10 20:20:53 +00:00
selectedTopics: shoutTopics,
mainTopic: shoutTopics.find((topic) => topic.slug === props.shout.mainTopic) || EMPTY_TOPIC,
2023-04-11 13:57:48 +00:00
body: props.shout.body,
coverImageUrl: props.shout.cover
2023-03-23 17:15:50 +00:00
})
onMount(async () => {
const allTopics = await apiClient.getAllTopics()
setTopics(allTopics)
})
2023-05-07 19:33:20 +00:00
onMount(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 0)
}
window.addEventListener('scroll', handleScroll, { passive: true })
onCleanup(() => {
window.removeEventListener('scroll', handleScroll)
})
})
2023-05-11 11:33:01 +00:00
const handleTitleInputChange = (value) => {
setForm('title', value)
2023-03-26 18:31:34 +00:00
2023-05-11 11:33:01 +00:00
if (value) {
2023-05-05 20:05:50 +00:00
setFormErrors('title', '')
2023-03-26 18:31:34 +00:00
}
}
const handleSlugInputChange = (e) => {
const slug = e.currentTarget.value
setForm('slug', slug)
}
2023-05-09 11:25:40 +00:00
const handleUploadModalContentCloseSetCover = (imgUrl: string) => {
2023-05-09 04:58:00 +00:00
hideModal()
setCoverImage(imageProxy(imgUrl))
2023-05-09 04:58:00 +00:00
setForm('coverImageUrl', imgUrl)
}
2023-05-10 14:07:41 +00:00
const handleDeleteCoverImage = () => {
setForm('coverImageUrl', '')
setCoverImage(null)
}
2023-05-09 04:58:00 +00:00
2023-05-10 20:20:53 +00:00
const handleTopicSelectChange = (newSelectedTopics) => {
console.log({ newSelectedTopics })
if (newSelectedTopics.length === 0) {
setForm('mainTopic', EMPTY_TOPIC)
} else if (
form.selectedTopics.length === 0 ||
newSelectedTopics.every((topic) => topic.id !== form.mainTopic.id)
) {
setForm('mainTopic', newSelectedTopics[0])
}
if (newSelectedTopics.length > 0) {
setFormErrors('selectedTopics', '')
}
setForm('selectedTopics', newSelectedTopics)
}
2022-09-09 11:53:35 +00:00
return (
2023-04-06 21:40:34 +00:00
<>
2023-05-07 19:33:20 +00:00
<button
class={clsx(styles.scrollTopButton, {
[styles.visible]: isScrolled()
})}
onClick={scrollTop}
>
2023-05-06 15:04:50 +00:00
<Icon name="up-button" class={styles.icon} />
<span class={styles.scrollTopButtonLabel}>{t('Scroll up')}</span>
</button>
2023-04-06 21:40:34 +00:00
<div class={styles.container}>
<Title>{t('Write an article')}</Title>
2023-05-05 20:05:50 +00:00
<form>
2023-04-06 21:40:34 +00:00
<div class="wide-container">
2023-03-29 08:51:27 +00:00
<div class="row">
2023-04-06 21:40:34 +00:00
<div class="col-md-19 col-lg-18 col-xl-16 offset-md-5">
2023-03-29 08:51:27 +00:00
<div
2023-04-11 13:57:48 +00:00
class={clsx(styles.edit, {
[styles.visible]: page().route === 'edit'
2023-03-29 08:51:27 +00:00
})}
>
<div class={styles.inputContainer}>
2023-05-11 11:33:01 +00:00
<GrowingTextarea
value={(value) => handleTitleInputChange(value)}
class={styles.titleInput}
2023-05-08 18:23:51 +00:00
placeholder={t('Header')}
2023-05-11 11:33:01 +00:00
initialValue={form.title}
/>
<Show when={formErrors.title}>
<div class={styles.validationError}>{formErrors.title}</div>
</Show>
</div>
2023-05-11 11:33:01 +00:00
<GrowingTextarea
value={(value) => setForm('subtitle', value)}
2023-03-29 08:51:27 +00:00
class={styles.subtitleInput}
2023-05-08 18:23:51 +00:00
placeholder={t('Subheader')}
2023-05-11 11:33:01 +00:00
initialValue={form.subtitle}
2023-03-29 08:51:27 +00:00
/>
2023-04-11 13:57:48 +00:00
<Editor
shoutId={props.shout.id}
2023-05-03 16:13:48 +00:00
initialContent={props.shout.body}
2023-04-11 13:57:48 +00:00
onChange={(body) => setForm('body', body)}
/>
2023-03-29 08:51:27 +00:00
</div>
<div
2023-04-11 13:57:48 +00:00
class={clsx(styles.editSettings, {
[styles.visible]: page().route === 'editSettings'
2023-03-29 08:51:27 +00:00
})}
>
2023-05-08 18:23:51 +00:00
<h1>{t('Publish Settings')}</h1>
2023-03-29 08:51:27 +00:00
<h4>Slug</h4>
<div class="pretty-form__item">
2023-03-23 17:15:50 +00:00
<input
type="text"
2023-03-29 08:51:27 +00:00
name="slug"
id="slug"
value={form.slug}
onChange={handleSlugInputChange}
2023-03-23 17:15:50 +00:00
/>
2023-03-29 08:51:27 +00:00
<label for="slug">Slug</label>
2023-03-26 18:31:34 +00:00
</div>
2023-03-29 08:51:27 +00:00
{/*<h4>Лид</h4>*/}
{/*<div class="pretty-form__item">*/}
{/* <textarea name="lead" id="lead" placeholder="Лид"></textarea>*/}
{/* <label for="lead">Лид</label>*/}
{/*</div>*/}
{/*<h4>Выбор сообщества</h4>*/}
{/*<p class="description">Сообщества можно перечислить через запятую</p>*/}
{/*<div class="pretty-form__item">*/}
{/* <input*/}
{/* type="text"*/}
{/* name="community"*/}
{/* id="community"*/}
{/* placeholder="Сообщества"*/}
{/* class="nolabel"*/}
{/* />*/}
{/*</div>*/}
<h4>Темы</h4>
{/*<p class="description">*/}
{/* Добавьте несколько тем, чтобы читатель знал, о&nbsp;чем ваш материал, и&nbsp;мог найти*/}
{/* его на&nbsp;страницах интересных ему тем. Темы можно менять местами, первая тема*/}
{/* становится заглавной*/}
{/*</p>*/}
2023-05-11 11:06:29 +00:00
<div class={styles.inputContainer}>
<div class={clsx('pretty-form__item', styles.topicSelectContainer)}>
<Show when={topics()}>
<TopicSelect
topics={topics()}
onChange={handleTopicSelectChange}
selectedTopics={form.selectedTopics}
onMainTopicChange={(mainTopic) => setForm('mainTopic', mainTopic)}
mainTopic={form.mainTopic}
/>
</Show>
</div>
<Show when={formErrors.selectedTopics}>
<div class={styles.validationError}>{formErrors.selectedTopics}</div>
2023-03-29 08:51:27 +00:00
</Show>
</div>
{/*<h4>Соавторы</h4>*/}
{/*<p class="description">У каждого соавтора можно добавить роль</p>*/}
{/*<div class="pretty-form__item--with-button">*/}
{/* <div class="pretty-form__item">*/}
{/* <input type="text" name="authors" id="authors" placeholder="Введите имя или e-mail" />*/}
{/* <label for="authors">Введите имя или e-mail</label>*/}
{/* </div>*/}
{/* <button class="button button--submit">Добавить</button>*/}
{/*</div>*/}
{/*<div class="row">*/}
{/* <div class="col-md-6">Михаил Драбкин</div>*/}
{/* <div class="col-md-6">*/}
{/* <input type="text" name="coauthor" id="coauthor1" class="nolabel" />*/}
{/* </div>*/}
{/*</div>*/}
2023-05-08 18:23:51 +00:00
<h4>{t('Material card')}</h4>
2023-03-29 08:51:27 +00:00
<p class="description">
2023-05-08 18:23:51 +00:00
{t(
'Choose a title image for the article. You can immediately see how the publication card will look like.'
)}
2023-03-29 08:51:27 +00:00
</p>
2023-05-08 18:23:51 +00:00
<div class={styles.articlePreview}>
2023-05-10 14:07:41 +00:00
<div class={styles.actions}>
<Button
variant="primary"
2023-05-11 11:43:14 +00:00
onClick={() => showModal('uploadCoverImage')}
2023-05-10 14:07:41 +00:00
value={coverImage() || form.coverImageUrl ? t('Add another image') : t('Add image')}
/>
<Show when={coverImage() ?? form.coverImageUrl}>
<Button variant="secondary" onClick={handleDeleteCoverImage} value={t('Delete')} />
</Show>
</div>
2023-05-09 04:58:00 +00:00
<Show when={coverImage() ?? form.coverImageUrl}>
<div class={styles.shoutCardCoverContainer}>
<div class={styles.shoutCardCover}>
<img
src={coverImage() || imageProxy(form.coverImageUrl)}
alt={form.title}
loading="lazy"
/>
2023-05-09 04:58:00 +00:00
</div>
</div>
</Show>
2023-05-08 18:23:51 +00:00
<div class={styles.shoutCardTitle}>{form.title}</div>
<div class={styles.shoutCardSubtitle}>{form.subtitle}</div>
<div class={styles.shoutAuthor}>{user().name}</div>
</div>
</div>
</div>
</div>
</div>
2023-04-06 21:40:34 +00:00
</form>
</div>
2023-05-11 11:43:14 +00:00
<Modal variant="narrow" name="uploadCoverImage">
2023-05-09 11:25:40 +00:00
<UploadModalContent onClose={(value) => handleUploadModalContentCloseSetCover(value)} />
2023-05-09 04:58:00 +00:00
</Modal>
2023-05-08 17:21:06 +00:00
<Panel shoutId={props.shout.id} />
2023-04-06 21:40:34 +00:00
</>
2022-09-09 11:53:35 +00:00
)
}
2022-11-01 19:27:43 +00:00
2023-04-11 13:57:48 +00:00
export default EditView