2023-03-12 20:16:20 +00:00
|
|
|
|
import { clsx } from 'clsx'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
import deepEqual from 'fast-deep-equal'
|
2024-06-24 17:50:27 +00:00
|
|
|
|
import {
|
|
|
|
|
Accessor,
|
|
|
|
|
Show,
|
|
|
|
|
createEffect,
|
|
|
|
|
createMemo,
|
|
|
|
|
createSignal,
|
|
|
|
|
lazy,
|
|
|
|
|
on,
|
|
|
|
|
onCleanup,
|
2024-06-26 08:22:05 +00:00
|
|
|
|
onMount
|
2024-06-24 17:50:27 +00:00
|
|
|
|
} from 'solid-js'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
import { createStore } from 'solid-js/store'
|
2024-05-18 23:22:19 +00:00
|
|
|
|
import { debounce } from 'throttle-debounce'
|
2024-07-04 07:51:15 +00:00
|
|
|
|
import { DropArea } from '~/components/_shared/DropArea'
|
|
|
|
|
import { Icon } from '~/components/_shared/Icon'
|
|
|
|
|
import { InviteMembers } from '~/components/_shared/InviteMembers'
|
|
|
|
|
import { Loading } from '~/components/_shared/Loading'
|
|
|
|
|
import { Popover } from '~/components/_shared/Popover'
|
|
|
|
|
import { EditorSwiper } from '~/components/_shared/SolidSwiper'
|
2024-07-30 19:06:17 +00:00
|
|
|
|
import { coreApiUrl } from '~/config'
|
2024-07-04 07:51:15 +00:00
|
|
|
|
import { ShoutForm, useEditorContext } from '~/context/editor'
|
|
|
|
|
import { useLocalize } from '~/context/localize'
|
2024-07-30 19:06:17 +00:00
|
|
|
|
import { useSession } from '~/context/session'
|
|
|
|
|
import { graphqlClientCreate } from '~/graphql/client'
|
2024-06-24 17:50:27 +00:00
|
|
|
|
import getMyShoutQuery from '~/graphql/query/core/article-my'
|
2024-07-04 07:51:15 +00:00
|
|
|
|
import type { Shout, Topic } from '~/graphql/schema/core.gen'
|
2024-07-05 14:08:12 +00:00
|
|
|
|
import { slugify } from '~/intl/translit'
|
2024-07-13 12:25:25 +00:00
|
|
|
|
import { getImageUrl } from '~/lib/getThumbUrl'
|
2024-07-13 11:42:53 +00:00
|
|
|
|
import { isDesktop } from '~/lib/mediaQuery'
|
2024-06-24 17:50:27 +00:00
|
|
|
|
import { LayoutType } from '~/types/common'
|
|
|
|
|
import { MediaItem } from '~/types/mediaitem'
|
2024-07-04 07:51:15 +00:00
|
|
|
|
import { clone } from '~/utils/clone'
|
2024-02-13 13:09:44 +00:00
|
|
|
|
import { Editor, Panel } from '../../Editor'
|
|
|
|
|
import { AudioUploader } from '../../Editor/AudioUploader'
|
|
|
|
|
import { AutoSaveNotice } from '../../Editor/AutoSaveNotice'
|
|
|
|
|
import { VideoUploader } from '../../Editor/VideoUploader'
|
2024-07-21 02:17:42 +00:00
|
|
|
|
import { Modal } from '../../_shared/Modal'
|
|
|
|
|
import { TableOfContents } from '../../_shared/TableOfContents'
|
2024-05-30 18:35:51 +00:00
|
|
|
|
import styles from './EditView.module.scss'
|
2024-02-13 13:09:44 +00:00
|
|
|
|
|
|
|
|
|
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
|
2024-07-04 07:51:15 +00:00
|
|
|
|
const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea'))
|
2023-11-18 14:10:02 +00:00
|
|
|
|
|
2023-06-10 14:10:05 +00:00
|
|
|
|
type Props = {
|
2023-04-11 13:57:48 +00:00
|
|
|
|
shout: Shout
|
|
|
|
|
}
|
2023-08-12 07:48:43 +00:00
|
|
|
|
|
2023-08-17 04:31:03 +00:00
|
|
|
|
export const MAX_HEADER_LIMIT = 100
|
2023-08-12 07:48:43 +00:00
|
|
|
|
export const EMPTY_TOPIC: Topic = {
|
|
|
|
|
id: -1,
|
2024-06-26 08:22:05 +00:00
|
|
|
|
slug: ''
|
2023-08-12 07:48:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-18 23:22:19 +00:00
|
|
|
|
const AUTO_SAVE_DELAY = 3000
|
|
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
|
const handleScrollTopButtonClick = (ev: MouseEvent | TouchEvent) => {
|
|
|
|
|
ev.preventDefault()
|
2024-07-06 06:41:16 +00:00
|
|
|
|
window?.scrollTo({
|
2023-05-09 05:05:06 +00:00
|
|
|
|
top: 0,
|
2024-06-26 08:22:05 +00:00
|
|
|
|
behavior: 'smooth'
|
2023-05-09 05:05:06 +00:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-10 14:10:05 +00:00
|
|
|
|
export const EditView = (props: Props) => {
|
2023-02-17 09:21:02 +00:00
|
|
|
|
const { t } = useLocalize()
|
2023-05-07 19:33:20 +00:00
|
|
|
|
const [isScrolled, setIsScrolled] = createSignal(false)
|
2024-07-30 19:06:17 +00:00
|
|
|
|
const { session } = useSession()
|
|
|
|
|
const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token))
|
|
|
|
|
|
2023-05-04 19:57:02 +00:00
|
|
|
|
const {
|
|
|
|
|
form,
|
2023-05-05 20:05:50 +00:00
|
|
|
|
formErrors,
|
2024-02-04 17:40:15 +00:00
|
|
|
|
setForm,
|
|
|
|
|
setFormErrors,
|
|
|
|
|
saveDraft,
|
|
|
|
|
saveDraftToLocalStorage,
|
2024-06-26 08:22:05 +00:00
|
|
|
|
getDraftFromLocalStorage
|
2023-05-04 19:57:02 +00:00
|
|
|
|
} = useEditorContext()
|
2024-06-24 17:50:27 +00:00
|
|
|
|
const [shoutTopics, setShoutTopics] = createSignal<Topic[]>([])
|
|
|
|
|
const [draft, setDraft] = createSignal()
|
|
|
|
|
let subtitleInput: HTMLTextAreaElement | null
|
2023-08-14 15:50:03 +00:00
|
|
|
|
const [prevForm, setPrevForm] = createStore<ShoutForm>(clone(form))
|
2023-08-04 15:59:36 +00:00
|
|
|
|
const [saving, setSaving] = createSignal(false)
|
2023-08-17 04:31:03 +00:00
|
|
|
|
const [isSubtitleVisible, setIsSubtitleVisible] = createSignal(Boolean(form.subtitle))
|
|
|
|
|
const [isLeadVisible, setIsLeadVisible] = createSignal(Boolean(form.lead))
|
2024-06-24 17:50:27 +00:00
|
|
|
|
const mediaItems: Accessor<MediaItem[]> = createMemo(() => JSON.parse(form.media || '[]'))
|
|
|
|
|
|
|
|
|
|
createEffect(
|
|
|
|
|
on(
|
|
|
|
|
() => props.shout,
|
|
|
|
|
(shout) => {
|
|
|
|
|
if (shout) {
|
2024-07-31 21:41:15 +00:00
|
|
|
|
// console.debug(`[EditView] shout is loaded: ${shout}`)
|
2024-06-24 17:50:27 +00:00
|
|
|
|
setShoutTopics((shout.topics as Topic[]) || [])
|
|
|
|
|
const stored = getDraftFromLocalStorage(shout.id)
|
|
|
|
|
if (stored) {
|
2024-07-31 21:41:15 +00:00
|
|
|
|
// console.info(`[EditView] got stored shout: ${stored}`)
|
2024-06-24 17:50:27 +00:00
|
|
|
|
setDraft(stored)
|
|
|
|
|
} else {
|
|
|
|
|
if (!shout.slug) {
|
|
|
|
|
console.warn(`[EditView] shout has no slug! ${shout}`)
|
|
|
|
|
}
|
|
|
|
|
const draftForm = {
|
|
|
|
|
slug: shout.slug || '',
|
|
|
|
|
shoutId: shout.id || 0,
|
|
|
|
|
title: shout.title || '',
|
|
|
|
|
lead: shout.lead || '',
|
|
|
|
|
description: shout.description || '',
|
|
|
|
|
subtitle: shout.subtitle || '',
|
|
|
|
|
selectedTopics: (shoutTopics() || []) as Topic[],
|
|
|
|
|
mainTopic: shoutTopics()[0] || '',
|
|
|
|
|
body: shout.body || '',
|
|
|
|
|
coverImageUrl: shout.cover || '',
|
|
|
|
|
media: shout.media || '',
|
2024-06-26 08:22:05 +00:00
|
|
|
|
layout: shout.layout
|
2024-06-24 17:50:27 +00:00
|
|
|
|
}
|
|
|
|
|
setForm((_) => draftForm)
|
|
|
|
|
console.debug('draft from props data: ', draftForm)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
2024-06-26 08:22:05 +00:00
|
|
|
|
{ defer: true }
|
|
|
|
|
)
|
2024-06-24 17:50:27 +00:00
|
|
|
|
)
|
2023-03-23 17:15:50 +00:00
|
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
|
createEffect(
|
|
|
|
|
on(
|
|
|
|
|
draft,
|
|
|
|
|
(d) => {
|
|
|
|
|
if (d) {
|
2024-09-15 18:47:14 +00:00
|
|
|
|
const draftForm = Object.keys(d) ? d : { shoutId: props.shout.id }
|
2024-06-24 17:50:27 +00:00
|
|
|
|
setForm(draftForm)
|
|
|
|
|
console.debug('draft from localstorage: ', draftForm)
|
|
|
|
|
}
|
|
|
|
|
},
|
2024-06-26 08:22:05 +00:00
|
|
|
|
{ defer: true }
|
|
|
|
|
)
|
2024-06-24 17:50:27 +00:00
|
|
|
|
)
|
2023-07-02 05:08:42 +00:00
|
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
|
createEffect(
|
|
|
|
|
on(
|
|
|
|
|
() => props.shout?.id,
|
|
|
|
|
async (shoutId) => {
|
|
|
|
|
if (shoutId) {
|
2024-07-30 19:06:17 +00:00
|
|
|
|
const resp = await client()?.query(getMyShoutQuery, { shout_id: shoutId })
|
2024-06-24 17:50:27 +00:00
|
|
|
|
const result = resp?.data?.get_my_shout
|
|
|
|
|
if (result) {
|
2024-07-31 21:41:15 +00:00
|
|
|
|
// console.debug('[EditView] getMyShout result: ', result)
|
2024-06-24 17:50:27 +00:00
|
|
|
|
const { shout: loadedShout, error } = result
|
|
|
|
|
setDraft(loadedShout)
|
2024-07-31 21:41:15 +00:00
|
|
|
|
// console.debug('[EditView] loadedShout:', loadedShout)
|
|
|
|
|
error && console.log(error)
|
2024-06-24 17:50:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
2024-06-26 08:22:05 +00:00
|
|
|
|
{ defer: true }
|
|
|
|
|
)
|
2024-06-24 17:50:27 +00:00
|
|
|
|
)
|
2024-05-18 23:22:19 +00:00
|
|
|
|
|
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)
|
|
|
|
|
})
|
2024-05-18 23:22:19 +00:00
|
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
|
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
2023-08-14 15:50:03 +00:00
|
|
|
|
if (!deepEqual(prevForm, form)) {
|
|
|
|
|
event.returnValue = t(
|
2024-06-26 08:22:05 +00:00
|
|
|
|
'There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?'
|
2023-08-14 15:50:03 +00:00
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.addEventListener('beforeunload', handleBeforeUnload)
|
|
|
|
|
onCleanup(() => window.removeEventListener('beforeunload', handleBeforeUnload))
|
|
|
|
|
})
|
|
|
|
|
|
2024-02-13 13:09:44 +00:00
|
|
|
|
const handleTitleInputChange = (value: string) => {
|
2024-05-18 23:22:19 +00:00
|
|
|
|
handleInputChange('title', value)
|
|
|
|
|
handleInputChange('slug', slugify(value))
|
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
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
|
const handleAddMedia = (data: MediaItem[]) => {
|
2023-07-14 13:06:21 +00:00
|
|
|
|
const newMedia = [...mediaItems(), ...data]
|
2024-05-18 23:22:19 +00:00
|
|
|
|
handleInputChange('media', JSON.stringify(newMedia))
|
2023-07-02 05:08:42 +00:00
|
|
|
|
}
|
2024-06-24 17:50:27 +00:00
|
|
|
|
const handleSortedMedia = (data: MediaItem[]) => {
|
2024-05-18 23:22:19 +00:00
|
|
|
|
handleInputChange('media', JSON.stringify(data))
|
2023-07-02 05:08:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
|
const handleMediaDelete = (index: number) => {
|
2023-07-02 05:08:42 +00:00
|
|
|
|
const copy = [...mediaItems()]
|
2024-06-02 10:37:54 +00:00
|
|
|
|
if (copy?.length > 0) copy.splice(index, 1)
|
2024-05-18 23:22:19 +00:00
|
|
|
|
handleInputChange('media', JSON.stringify(copy))
|
2023-07-02 05:08:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
|
const handleMediaChange = (index: number, value: MediaItem) => {
|
2023-07-02 05:08:42 +00:00
|
|
|
|
const updated = mediaItems().map((item, idx) => (idx === index ? value : item))
|
2024-05-18 23:22:19 +00:00
|
|
|
|
handleInputChange('media', JSON.stringify(updated))
|
2023-06-10 14:10:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-15 21:51:38 +00:00
|
|
|
|
const [baseAudioFields, setBaseAudioFields] = createSignal({
|
|
|
|
|
artist: '',
|
|
|
|
|
date: '',
|
2024-06-26 08:22:05 +00:00
|
|
|
|
genre: ''
|
2023-07-15 21:51:38 +00:00
|
|
|
|
})
|
|
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
|
const handleBaseFieldsChange = (key: string, value: string) => {
|
2023-07-15 21:51:38 +00:00
|
|
|
|
if (mediaItems().length > 0) {
|
|
|
|
|
const updated = mediaItems().map((media) => ({ ...media, [key]: value }))
|
2024-05-18 23:22:19 +00:00
|
|
|
|
handleInputChange('media', JSON.stringify(updated))
|
2023-07-15 21:51:38 +00:00
|
|
|
|
} else {
|
|
|
|
|
setBaseAudioFields({ ...baseAudioFields(), [key]: value })
|
|
|
|
|
}
|
2023-07-14 13:06:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const articleTitle = () => {
|
|
|
|
|
switch (props.shout.layout as LayoutType) {
|
2023-11-28 13:18:25 +00:00
|
|
|
|
case 'audio': {
|
2023-07-14 13:06:21 +00:00
|
|
|
|
return t('Album name')
|
|
|
|
|
}
|
|
|
|
|
case 'image': {
|
|
|
|
|
return t('Gallery name')
|
|
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
return t('Header')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-06-24 17:50:27 +00:00
|
|
|
|
const [hasChanges, setHasChanges] = createSignal(false)
|
2024-05-01 14:33:37 +00:00
|
|
|
|
const autoSave = async () => {
|
2024-05-18 23:22:19 +00:00
|
|
|
|
console.log('autoSave called')
|
|
|
|
|
if (hasChanges()) {
|
2024-05-03 13:38:12 +00:00
|
|
|
|
console.debug('saving draft', form)
|
2024-05-01 14:33:37 +00:00
|
|
|
|
setSaving(true)
|
2024-05-03 13:38:12 +00:00
|
|
|
|
saveDraftToLocalStorage(form)
|
|
|
|
|
await saveDraft(form)
|
2024-05-01 14:33:37 +00:00
|
|
|
|
setPrevForm(clone(form))
|
2024-05-18 23:22:19 +00:00
|
|
|
|
setSaving(false)
|
|
|
|
|
setHasChanges(false)
|
2024-05-01 14:33:37 +00:00
|
|
|
|
}
|
2023-08-04 15:59:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-18 23:22:19 +00:00
|
|
|
|
const debouncedAutoSave = debounce(AUTO_SAVE_DELAY, autoSave)
|
2024-05-01 14:33:37 +00:00
|
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
|
const handleInputChange = (key: keyof ShoutForm, value: string) => {
|
2024-05-18 23:22:19 +00:00
|
|
|
|
console.log(`[handleInputChange] ${key}: ${value}`)
|
|
|
|
|
setForm(key, value)
|
|
|
|
|
setHasChanges(true)
|
|
|
|
|
debouncedAutoSave()
|
2023-08-04 15:59:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMount(() => {
|
2024-05-18 23:22:19 +00:00
|
|
|
|
onCleanup(() => {
|
|
|
|
|
debouncedAutoSave.cancel()
|
|
|
|
|
})
|
2023-08-04 15:59:36 +00:00
|
|
|
|
})
|
|
|
|
|
|
2023-08-17 04:31:03 +00:00
|
|
|
|
const showSubtitleInput = () => {
|
|
|
|
|
setIsSubtitleVisible(true)
|
2024-06-24 17:50:27 +00:00
|
|
|
|
subtitleInput?.focus()
|
2023-08-17 04:31:03 +00:00
|
|
|
|
}
|
2024-05-01 14:33:37 +00:00
|
|
|
|
|
2023-08-17 04:31:03 +00:00
|
|
|
|
const showLeadInput = () => {
|
|
|
|
|
setIsLeadVisible(true)
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-09 11:53:35 +00:00
|
|
|
|
return (
|
2023-04-06 21:40:34 +00:00
|
|
|
|
<>
|
|
|
|
|
<div class={styles.container}>
|
2023-05-05 20:05:50 +00:00
|
|
|
|
<form>
|
2023-04-06 21:40:34 +00:00
|
|
|
|
<div class="wide-container">
|
2023-05-11 11:52:56 +00:00
|
|
|
|
<button
|
|
|
|
|
class={clsx(styles.scrollTopButton, {
|
2024-06-26 08:22:05 +00:00
|
|
|
|
[styles.visible]: isScrolled()
|
2023-05-11 11:52:56 +00:00
|
|
|
|
})}
|
2023-05-12 13:03:46 +00:00
|
|
|
|
onClick={handleScrollTopButtonClick}
|
2023-05-11 11:52:56 +00:00
|
|
|
|
>
|
|
|
|
|
<Icon name="up-button" class={styles.icon} />
|
|
|
|
|
<span class={styles.scrollTopButtonLabel}>{t('Scroll up')}</span>
|
|
|
|
|
</button>
|
|
|
|
|
|
2023-08-23 22:31:39 +00:00
|
|
|
|
<AutoSaveNotice active={saving()} />
|
|
|
|
|
|
2023-08-31 13:41:34 +00:00
|
|
|
|
<div class={styles.wrapperTableOfContents}>
|
|
|
|
|
<Show when={isDesktop() && form.body}>
|
|
|
|
|
<TableOfContents variant="editor" parentSelector="#editorBody" body={form.body} />
|
|
|
|
|
</Show>
|
|
|
|
|
</div>
|
2023-08-12 07:48:43 +00:00
|
|
|
|
|
2023-08-31 13:41:34 +00:00
|
|
|
|
<div class="row">
|
|
|
|
|
<div class="col-md-19 col-lg-18 col-xl-16 offset-md-5">
|
2024-07-03 21:25:03 +00:00
|
|
|
|
<Show when={props.shout}>
|
2023-08-31 13:41:34 +00:00
|
|
|
|
<div class={styles.headingActions}>
|
2023-11-28 13:18:25 +00:00
|
|
|
|
<Show when={!isSubtitleVisible() && props.shout.layout !== 'audio'}>
|
2023-08-31 13:41:34 +00:00
|
|
|
|
<div class={styles.action} onClick={showSubtitleInput}>
|
|
|
|
|
{t('Add subtitle')}
|
|
|
|
|
</div>
|
|
|
|
|
</Show>
|
2023-11-28 13:18:25 +00:00
|
|
|
|
<Show when={!isLeadVisible() && props.shout.layout !== 'audio'}>
|
2023-08-31 13:41:34 +00:00
|
|
|
|
<div class={styles.action} onClick={showLeadInput}>
|
|
|
|
|
{t('Add intro')}
|
|
|
|
|
</div>
|
|
|
|
|
</Show>
|
|
|
|
|
</div>
|
|
|
|
|
<>
|
2023-11-28 13:18:25 +00:00
|
|
|
|
<div class={clsx({ [styles.audioHeader]: props.shout.layout === 'audio' })}>
|
2023-08-31 13:41:34 +00:00
|
|
|
|
<div class={styles.inputContainer}>
|
|
|
|
|
<GrowingTextarea
|
|
|
|
|
allowEnterKey={true}
|
|
|
|
|
value={(value) => handleTitleInputChange(value)}
|
|
|
|
|
class={styles.titleInput}
|
|
|
|
|
placeholder={articleTitle()}
|
|
|
|
|
initialValue={form.title}
|
|
|
|
|
maxLength={MAX_HEADER_LIMIT}
|
|
|
|
|
/>
|
2023-08-12 07:48:43 +00:00
|
|
|
|
|
2023-08-31 13:41:34 +00:00
|
|
|
|
<Show when={formErrors.title}>
|
|
|
|
|
<div class={styles.validationError}>{formErrors.title}</div>
|
|
|
|
|
</Show>
|
2023-08-31 09:11:32 +00:00
|
|
|
|
|
2023-11-28 13:18:25 +00:00
|
|
|
|
<Show when={props.shout.layout === 'audio'}>
|
2023-08-31 13:41:34 +00:00
|
|
|
|
<div class={styles.additional}>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
placeholder={t('Artist...')}
|
|
|
|
|
class={styles.additionalInput}
|
|
|
|
|
value={mediaItems()[0]?.artist || ''}
|
|
|
|
|
onChange={(event) => handleBaseFieldsChange('artist', event.target.value)}
|
|
|
|
|
/>
|
|
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
min="1900"
|
|
|
|
|
max={new Date().getFullYear()}
|
|
|
|
|
step="1"
|
|
|
|
|
class={styles.additionalInput}
|
|
|
|
|
placeholder={t('Release date...')}
|
|
|
|
|
value={mediaItems()[0]?.date || ''}
|
|
|
|
|
onChange={(event) => handleBaseFieldsChange('date', event.target.value)}
|
|
|
|
|
/>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
placeholder={t('Genre...')}
|
|
|
|
|
class={styles.additionalInput}
|
|
|
|
|
value={mediaItems()[0]?.genre || ''}
|
|
|
|
|
onChange={(event) => handleBaseFieldsChange('genre', event.target.value)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</Show>
|
2023-11-28 13:18:25 +00:00
|
|
|
|
<Show when={props.shout.layout !== 'audio'}>
|
2023-08-31 13:41:34 +00:00
|
|
|
|
<Show when={isSubtitleVisible()}>
|
|
|
|
|
<GrowingTextarea
|
2024-06-24 17:50:27 +00:00
|
|
|
|
textAreaRef={(el) => (subtitleInput = el)}
|
2023-08-31 13:41:34 +00:00
|
|
|
|
allowEnterKey={false}
|
2024-05-18 23:22:19 +00:00
|
|
|
|
value={(value) => handleInputChange('subtitle', value || '')}
|
2023-08-31 13:41:34 +00:00
|
|
|
|
class={styles.subtitleInput}
|
|
|
|
|
placeholder={t('Subheader')}
|
2024-02-17 15:13:54 +00:00
|
|
|
|
initialValue={form.subtitle || ''}
|
2023-08-31 13:41:34 +00:00
|
|
|
|
maxLength={MAX_HEADER_LIMIT}
|
|
|
|
|
/>
|
|
|
|
|
</Show>
|
|
|
|
|
<Show when={isLeadVisible()}>
|
|
|
|
|
<SimplifiedEditor
|
|
|
|
|
variant="minimal"
|
|
|
|
|
onlyBubbleControls={true}
|
|
|
|
|
smallHeight={true}
|
|
|
|
|
placeholder={t('A short introduction to keep the reader interested')}
|
|
|
|
|
initialContent={form.lead}
|
2024-05-18 23:22:19 +00:00
|
|
|
|
onChange={(value) => handleInputChange('lead', value)}
|
2023-08-17 04:31:03 +00:00
|
|
|
|
/>
|
|
|
|
|
</Show>
|
2023-08-12 07:48:43 +00:00
|
|
|
|
</Show>
|
|
|
|
|
</div>
|
2023-11-28 13:18:25 +00:00
|
|
|
|
<Show when={props.shout.layout === 'audio'}>
|
2023-08-31 13:41:34 +00:00
|
|
|
|
<Show
|
|
|
|
|
when={form.coverImageUrl}
|
|
|
|
|
fallback={
|
|
|
|
|
<DropArea
|
|
|
|
|
isSquare={true}
|
|
|
|
|
placeholder={t('Add cover')}
|
|
|
|
|
description={
|
|
|
|
|
<>
|
|
|
|
|
{t('min. 1400×1400 pix')}
|
|
|
|
|
<br />
|
|
|
|
|
{t('jpg, .png, max. 10 mb.')}
|
|
|
|
|
</>
|
|
|
|
|
}
|
|
|
|
|
isMultiply={false}
|
|
|
|
|
fileType={'image'}
|
2024-05-18 23:22:19 +00:00
|
|
|
|
onUpload={(val) => handleInputChange('coverImageUrl', val[0].url)}
|
2023-08-31 13:41:34 +00:00
|
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
class={styles.cover}
|
2023-10-27 18:50:13 +00:00
|
|
|
|
style={{
|
2024-06-24 17:50:27 +00:00
|
|
|
|
'background-image': `url(${getImageUrl(form.coverImageUrl || '', {
|
2024-06-26 08:22:05 +00:00
|
|
|
|
width: 1600
|
|
|
|
|
})})`
|
2023-10-27 18:50:13 +00:00
|
|
|
|
}}
|
2023-10-30 11:29:15 +00:00
|
|
|
|
>
|
|
|
|
|
<Popover content={t('Delete cover')}>
|
2024-06-24 17:50:27 +00:00
|
|
|
|
{(triggerRef: (_el: HTMLElement | null) => void) => (
|
2023-10-30 11:29:15 +00:00
|
|
|
|
<div
|
|
|
|
|
ref={triggerRef}
|
|
|
|
|
class={styles.delete}
|
2024-06-24 17:50:27 +00:00
|
|
|
|
onClick={() => handleInputChange('coverImageUrl', '')}
|
2023-10-30 11:29:15 +00:00
|
|
|
|
>
|
|
|
|
|
<Icon name="close-white" />
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</Popover>
|
|
|
|
|
</div>
|
2023-08-31 13:41:34 +00:00
|
|
|
|
</Show>
|
2023-08-31 09:11:32 +00:00
|
|
|
|
</Show>
|
2023-08-31 13:41:34 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Show when={props.shout.layout === 'image'}>
|
2023-12-04 10:05:29 +00:00
|
|
|
|
<EditorSwiper
|
2023-08-31 13:41:34 +00:00
|
|
|
|
images={mediaItems()}
|
|
|
|
|
onImageChange={handleMediaChange}
|
|
|
|
|
onImageDelete={(index) => handleMediaDelete(index)}
|
2024-06-24 17:50:27 +00:00
|
|
|
|
onImagesAdd={(value: MediaItem[]) => handleAddMedia(value)}
|
2023-08-31 13:41:34 +00:00
|
|
|
|
onImagesSorted={(value) => handleSortedMedia(value)}
|
|
|
|
|
/>
|
|
|
|
|
</Show>
|
|
|
|
|
|
|
|
|
|
<Show when={props.shout.layout === 'video'}>
|
|
|
|
|
<VideoUploader
|
|
|
|
|
video={mediaItems()}
|
|
|
|
|
onVideoAdd={(data) => handleAddMedia(data)}
|
|
|
|
|
onVideoDelete={(index) => handleMediaDelete(index)}
|
|
|
|
|
/>
|
|
|
|
|
</Show>
|
|
|
|
|
|
2023-11-28 13:18:25 +00:00
|
|
|
|
<Show when={props.shout.layout === 'audio'}>
|
2023-08-31 13:41:34 +00:00
|
|
|
|
<AudioUploader
|
|
|
|
|
audio={mediaItems()}
|
|
|
|
|
baseFields={baseAudioFields()}
|
|
|
|
|
onAudioAdd={(value) => handleAddMedia(value)}
|
|
|
|
|
onAudioChange={handleMediaChange}
|
|
|
|
|
onAudioSorted={(value) => handleSortedMedia(value)}
|
|
|
|
|
/>
|
|
|
|
|
</Show>
|
|
|
|
|
</>
|
|
|
|
|
</Show>
|
2023-03-12 20:16:20 +00:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2024-07-03 21:25:03 +00:00
|
|
|
|
<Show when={form?.shoutId} fallback={<Loading />}>
|
2023-08-31 13:41:34 +00:00
|
|
|
|
<Editor
|
|
|
|
|
shoutId={form.shoutId}
|
|
|
|
|
initialContent={form.body}
|
2024-05-18 23:22:19 +00:00
|
|
|
|
onChange={(body) => handleInputChange('body', body)}
|
2023-08-31 13:41:34 +00:00
|
|
|
|
/>
|
|
|
|
|
</Show>
|
2023-03-12 20:16:20 +00:00
|
|
|
|
</div>
|
2023-04-06 21:40:34 +00:00
|
|
|
|
</form>
|
|
|
|
|
</div>
|
2024-06-24 17:50:27 +00:00
|
|
|
|
<Show when={props.shout}>
|
|
|
|
|
<Panel shoutId={props.shout.id} />
|
|
|
|
|
</Show>
|
2024-02-03 05:19:15 +00:00
|
|
|
|
|
|
|
|
|
<Modal variant="medium" name="inviteCoauthors">
|
|
|
|
|
<InviteMembers variant={'coauthors'} title={t('Invite experts')} />
|
|
|
|
|
</Modal>
|
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
|