Merge pull request #414 from Discours/hotfix/posting-author
posting author fixes
This commit is contained in:
commit
ae589e39fa
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -22,3 +22,4 @@ bun.lockb
|
||||||
/blob-report/
|
/blob-report/
|
||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
/plawright-report/
|
/plawright-report/
|
||||||
|
target
|
|
@ -378,6 +378,7 @@
|
||||||
"There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?": "There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?",
|
"There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?": "There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?",
|
||||||
"There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?": "There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?",
|
"There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?": "There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?",
|
||||||
"This comment has not yet been rated": "This comment has not yet been rated",
|
"This comment has not yet been rated": "This comment has not yet been rated",
|
||||||
|
"This content is not published yet": "This content is not published yet",
|
||||||
"This email is": "This email is",
|
"This email is": "This email is",
|
||||||
"This email is not verified": "This email is not verified",
|
"This email is not verified": "This email is not verified",
|
||||||
"This email is verified": "This email is verified",
|
"This email is verified": "This email is verified",
|
||||||
|
@ -524,6 +525,7 @@
|
||||||
"view": "view",
|
"view": "view",
|
||||||
"viewsWithCount": "{count} {count, plural, one {view} other {views}}",
|
"viewsWithCount": "{count} {count, plural, one {view} other {views}}",
|
||||||
"yesterday": "yesterday",
|
"yesterday": "yesterday",
|
||||||
|
"Failed to delete comment": "Failed to delete comment",
|
||||||
"It's OK. Just enter your email to receive a link to change your password": "It's OK. Just enter your email to receive a link to change your password",
|
"It's OK. Just enter your email to receive a link to change your password": "It's OK. Just enter your email to receive a link to change your password",
|
||||||
"Restore password": "Restore password"
|
"Restore password": "Restore password"
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,6 +149,7 @@
|
||||||
"Enter the code or click the link from email to confirm": "Введите код из письма или пройдите по ссылке в письме для подтверждения регистрации",
|
"Enter the code or click the link from email to confirm": "Введите код из письма или пройдите по ссылке в письме для подтверждения регистрации",
|
||||||
"Enter your new password": "Введите новый пароль",
|
"Enter your new password": "Введите новый пароль",
|
||||||
"Enter": "Войти",
|
"Enter": "Войти",
|
||||||
|
"This content is not published yet": "Содержимое ещё не опубликовано",
|
||||||
"Error": "Ошибка",
|
"Error": "Ошибка",
|
||||||
"Experience": "Личный опыт",
|
"Experience": "Личный опыт",
|
||||||
"FAQ": "Советы и предложения",
|
"FAQ": "Советы и предложения",
|
||||||
|
@ -529,6 +530,7 @@
|
||||||
"repeat": "повторить",
|
"repeat": "повторить",
|
||||||
"resend confirmation link": "отправить ссылку ещё раз",
|
"resend confirmation link": "отправить ссылку ещё раз",
|
||||||
"shout": "пост",
|
"shout": "пост",
|
||||||
|
"shout not found": "публикация не найдена",
|
||||||
"shoutsWithCount": "{count} {count, plural, one {пост} few {поста} other {постов}}",
|
"shoutsWithCount": "{count} {count, plural, one {пост} few {поста} other {постов}}",
|
||||||
"sign in": "войти",
|
"sign in": "войти",
|
||||||
"sign up or sign in": "зарегистрироваться или войти",
|
"sign up or sign in": "зарегистрироваться или войти",
|
||||||
|
@ -550,6 +552,7 @@
|
||||||
"view": "просмотр",
|
"view": "просмотр",
|
||||||
"viewsWithCount": "{count} {count, plural, one {просмотр} few {просмотрa} other {просмотров}}",
|
"viewsWithCount": "{count} {count, plural, one {просмотр} few {просмотрa} other {просмотров}}",
|
||||||
"yesterday": "вчера",
|
"yesterday": "вчера",
|
||||||
|
"Failed to delete comment": "Не удалось удалить комментарий",
|
||||||
"It's OK. Just enter your email to receive a link to change your password": "Ничего страшного. Просто укажите свою почту, чтобы получить ссылку для смены пароля",
|
"It's OK. Just enter your email to receive a link to change your password": "Ничего страшного. Просто укажите свою почту, чтобы получить ссылку для смены пароля",
|
||||||
"Restore password": "Восстановить пароль"
|
"Restore password": "Восстановить пароль"
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,14 +64,19 @@ export const Comment = (props: Props) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isConfirmed) {
|
if (isConfirmed) {
|
||||||
await deleteReaction(props.comment.id)
|
const { error } = await deleteReaction(props.comment.id)
|
||||||
// TODO: Учесть то что deleteReaction может вернуть error
|
const notificationType = error ? 'error' : 'success'
|
||||||
if (props.onDelete) {
|
const notificationMessage = error
|
||||||
|
? t('Failed to delete comment')
|
||||||
|
: t('Comment successfully deleted')
|
||||||
|
await showSnackbar({ type: notificationType, body: notificationMessage })
|
||||||
|
|
||||||
|
if (!error && props.onDelete) {
|
||||||
props.onDelete(props.comment.id)
|
props.onDelete(props.comment.id)
|
||||||
}
|
}
|
||||||
await showSnackbar({ body: t('Comment successfully deleted') })
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
await showSnackbar({ body: 'error' })
|
||||||
console.error('[deleteReaction]', error)
|
console.error('[deleteReaction]', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@ export const FullArticle = (props: Props) => {
|
||||||
|
|
||||||
const media = createMemo<MediaItem[]>(() => {
|
const media = createMemo<MediaItem[]>(() => {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(props.article.media)
|
return JSON.parse(props.article?.media || '[]')
|
||||||
} catch {
|
} catch {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,19 +67,19 @@ const getTitleAndSubtitle = (
|
||||||
subtitle: string
|
subtitle: string
|
||||||
} => {
|
} => {
|
||||||
let title = article.title
|
let title = article.title
|
||||||
let subtitle = article.subtitle
|
let subtitle: string = article.subtitle || ''
|
||||||
|
|
||||||
if (!subtitle) {
|
if (!subtitle) {
|
||||||
let tt = article.title?.split('. ') || []
|
let titleParts = article.title?.split('. ') || []
|
||||||
|
|
||||||
if (tt?.length === 1) {
|
if (titleParts?.length === 1) {
|
||||||
tt = article.title?.split(/{!|\?|:|;}\s/) || []
|
titleParts = article.title?.split(/{!|\?|:|;}\s/) || []
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tt && tt.length > 1) {
|
if (titleParts && titleParts.length > 1) {
|
||||||
const sep = article.title?.replace(tt[0], '').split(' ', 1)[0]
|
const sep = article.title?.replace(titleParts[0], '').split(' ', 1)[0]
|
||||||
title = tt[0] + (sep === '.' || sep === ':' ? '' : sep)
|
title = titleParts[0] + (sep === '.' || sep === ':' ? '' : sep)
|
||||||
subtitle = capitalize(article.title?.replace(tt[0] + sep, ''), true)
|
subtitle = capitalize(article.title?.replace(titleParts[0] + sep, ''), true) || ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
const { title, subtitle } = getTitleAndSubtitle(props.article)
|
const { title, subtitle } = getTitleAndSubtitle(props.article)
|
||||||
|
|
||||||
const formattedDate = createMemo<string>(() =>
|
const formattedDate = createMemo<string>(() =>
|
||||||
props.article.published_at ? formatDate(new Date(props.article.published_at * 1000)) : '',
|
props.article?.published_at ? formatDate(new Date(props.article.published_at * 1000)) : '',
|
||||||
)
|
)
|
||||||
|
|
||||||
const canEdit = createMemo(
|
const canEdit = createMemo(
|
||||||
|
@ -135,6 +135,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
scrollTo: 'comments',
|
scrollTo: 'comments',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
class={clsx(styles.shoutCard, props.settings?.additionalClass, {
|
class={clsx(styles.shoutCard, props.settings?.additionalClass, {
|
||||||
|
@ -153,7 +154,9 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
[aspectRatio()]: props.withAspectRatio,
|
[aspectRatio()]: props.withAspectRatio,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
{/* Cover Image */}
|
||||||
<Show when={!(props.settings?.noimage || props.settings?.isFeedMode)}>
|
<Show when={!(props.settings?.noimage || props.settings?.isFeedMode)}>
|
||||||
|
{/* Cover Image Container */}
|
||||||
<div class={styles.shoutCardCoverContainer}>
|
<div class={styles.shoutCardCoverContainer}>
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.shoutCardCover, {
|
class={clsx(styles.shoutCardCover, {
|
||||||
|
@ -178,7 +181,10 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
{/* Shout Card Content */}
|
||||||
<div class={styles.shoutCardContent}>
|
<div class={styles.shoutCardContent}>
|
||||||
|
{/* Shout Card Icon */}
|
||||||
<Show
|
<Show
|
||||||
when={
|
when={
|
||||||
props.article.layout &&
|
props.article.layout &&
|
||||||
|
@ -195,6 +201,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
{/* Main Topic */}
|
||||||
<Show when={!props.settings?.isGroup && mainTopicSlug}>
|
<Show when={!props.settings?.isGroup && mainTopicSlug}>
|
||||||
<CardTopic
|
<CardTopic
|
||||||
title={mainTopicTitle}
|
title={mainTopicTitle}
|
||||||
|
@ -205,6 +212,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
{/* Title and Subtitle */}
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.shoutCardTitlesContainer, {
|
class={clsx(styles.shoutCardTitlesContainer, {
|
||||||
[styles.shoutCardTitlesContainerFeedMode]: props.settings?.isFeedMode,
|
[styles.shoutCardTitlesContainerFeedMode]: props.settings?.isFeedMode,
|
||||||
|
@ -224,22 +232,23 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
</Show>
|
</Show>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Details */}
|
||||||
<Show when={!(props.settings?.noauthor && props.settings?.nodate)}>
|
<Show when={!(props.settings?.noauthor && props.settings?.nodate)}>
|
||||||
|
{/* Author and Date */}
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.shoutDetails, { [styles.shoutDetailsFeedMode]: props.settings?.isFeedMode })}
|
class={clsx(styles.shoutDetails, { [styles.shoutDetailsFeedMode]: props.settings?.isFeedMode })}
|
||||||
>
|
>
|
||||||
<Show when={!props.settings?.noauthor}>
|
<Show when={!props.settings?.noauthor}>
|
||||||
<div class={styles.shoutAuthor}>
|
<div class={styles.shoutAuthor}>
|
||||||
<For each={props.article.authors}>
|
<For each={props.article.authors}>
|
||||||
{(a: Author) => {
|
{(a: Author) => (
|
||||||
return (
|
<AuthorLink
|
||||||
<AuthorLink
|
size={'XS'}
|
||||||
size={'XS'}
|
author={a}
|
||||||
author={a}
|
isFloorImportant={props.settings.isFloorImportant || props.settings?.isWithCover}
|
||||||
isFloorImportant={props.settings.isFloorImportant || props.settings?.isWithCover}
|
/>
|
||||||
/>
|
)}
|
||||||
)
|
|
||||||
}}
|
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
@ -248,6 +257,8 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
<Show when={props.article.description}>
|
<Show when={props.article.description}>
|
||||||
<section class={styles.shoutCardDescription} innerHTML={props.article.description} />
|
<section class={styles.shoutCardDescription} innerHTML={props.article.description} />
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
@ -51,7 +51,15 @@ const DialogAvatar = (props: Props) => {
|
||||||
<Show when={Boolean(props.url)} fallback={<div class={styles.letter}>{nameFirstLetter()}</div>}>
|
<Show when={Boolean(props.url)} fallback={<div class={styles.letter}>{nameFirstLetter()}</div>}>
|
||||||
<div
|
<div
|
||||||
class={styles.imageHolder}
|
class={styles.imageHolder}
|
||||||
style={{ 'background-image': `url(${getImageUrl(props.url, { width: 40, height: 40 })})` }}
|
style={{
|
||||||
|
'background-image': `url(
|
||||||
|
${
|
||||||
|
props.url.includes('discours.io')
|
||||||
|
? getImageUrl(props.url, { width: 40, height: 40 })
|
||||||
|
: props.url
|
||||||
|
}
|
||||||
|
)`,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -48,7 +48,6 @@ export const RegisterForm = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = async (event: Event) => {
|
const handleSubmit = async (event: Event) => {
|
||||||
console.log('!!! handleSubmit:', handleSubmit)
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (passwordError()) {
|
if (passwordError()) {
|
||||||
setValidationErrors((errors) => ({ ...errors, password: passwordError() }))
|
setValidationErrors((errors) => ({ ...errors, password: passwordError() }))
|
||||||
|
@ -148,9 +147,10 @@ export const RegisterForm = () => {
|
||||||
...prev,
|
...prev,
|
||||||
email: (
|
email: (
|
||||||
<>
|
<>
|
||||||
{t('This email is registered')}. {t('You can')}{' '}
|
{t('This email is registered')}
|
||||||
|
{'. '}
|
||||||
<span class="link" onClick={() => changeSearchParams({ mode: 'send-reset-link' })}>
|
<span class="link" onClick={() => changeSearchParams({ mode: 'send-reset-link' })}>
|
||||||
{t('Set the new password').toLocaleLowerCase()}
|
{t('Set the new password')}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
@ -193,7 +193,7 @@ export const RegisterForm = () => {
|
||||||
disabled={Boolean(emailStatus())}
|
disabled={Boolean(emailStatus())}
|
||||||
placeholder={t('Full name')}
|
placeholder={t('Full name')}
|
||||||
autocomplete="one-time-code"
|
autocomplete="one-time-code"
|
||||||
onInput={(event) => handleNameInput(event.currentTarget.value)}
|
onChange={(event) => handleNameInput(event.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<label for="name">{t('Full name')}</label>
|
<label for="name">{t('Full name')}</label>
|
||||||
<Show when={validationErrors().fullName && !emailStatus()}>
|
<Show when={validationErrors().fullName && !emailStatus()}>
|
||||||
|
@ -226,8 +226,8 @@ export const RegisterForm = () => {
|
||||||
<PasswordField
|
<PasswordField
|
||||||
disableAutocomplete={true}
|
disableAutocomplete={true}
|
||||||
disabled={Boolean(emailStatus())}
|
disabled={Boolean(emailStatus())}
|
||||||
errorMessage={(err) => setPasswordError(err)}
|
errorMessage={(err) => !emailStatus() && setPasswordError(err)}
|
||||||
onInput={(value) => setPassword(value)}
|
onInput={(value) => setPassword(emailStatus() ? '' : value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { AuthModalSearchParams } from './types'
|
import type { AuthModalSearchParams } from './types'
|
||||||
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { JSX, Show, createSignal } from 'solid-js'
|
import { JSX, Show, createSignal, onMount } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
|
@ -72,6 +72,12 @@ export const SendResetLinkForm = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (email()) {
|
||||||
|
console.info('[SendResetLinkForm] email detected')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
|
@ -98,7 +104,7 @@ export const SendResetLinkForm = () => {
|
||||||
type="email"
|
type="email"
|
||||||
value={email()}
|
value={email()}
|
||||||
placeholder={t('Email')}
|
placeholder={t('Email')}
|
||||||
onInput={(event) => handleEmailInput(event.currentTarget.value)}
|
onChange={(event) => handleEmailInput(event.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<label for="email">{t('Email')}</label>
|
<label for="email">{t('Email')}</label>
|
||||||
<Show when={isUserNotFound()}>
|
<Show when={isUserNotFound()}>
|
||||||
|
|
|
@ -64,10 +64,11 @@ export const EditView = (props: Props) => {
|
||||||
getDraftFromLocalStorage,
|
getDraftFromLocalStorage,
|
||||||
} = useEditorContext()
|
} = useEditorContext()
|
||||||
const shoutTopics = props.shout.topics || []
|
const shoutTopics = props.shout.topics || []
|
||||||
const draft = getDraftFromLocalStorage(props.shout.id)
|
|
||||||
|
|
||||||
|
// TODO: проверить сохранение черновика в local storage (не работает)
|
||||||
|
const draft = getDraftFromLocalStorage(props.shout.id)
|
||||||
if (draft) {
|
if (draft) {
|
||||||
setForm(draft)
|
setForm(Object.keys(draft).length !== 0 ? draft : { shoutId: props.shout.id })
|
||||||
} else {
|
} else {
|
||||||
setForm({
|
setForm({
|
||||||
slug: props.shout.slug,
|
slug: props.shout.slug,
|
||||||
|
@ -179,6 +180,7 @@ export const EditView = (props: Props) => {
|
||||||
|
|
||||||
let autoSaveTimeOutId: number | string | NodeJS.Timeout
|
let autoSaveTimeOutId: number | string | NodeJS.Timeout
|
||||||
|
|
||||||
|
//TODO: add throttle
|
||||||
const autoSaveRecursive = () => {
|
const autoSaveRecursive = () => {
|
||||||
autoSaveTimeOutId = setTimeout(async () => {
|
autoSaveTimeOutId = setTimeout(async () => {
|
||||||
const hasChanges = !deepEqual(form, prevForm)
|
const hasChanges = !deepEqual(form, prevForm)
|
||||||
|
@ -307,10 +309,10 @@ export const EditView = (props: Props) => {
|
||||||
subtitleInput.current = el
|
subtitleInput.current = el
|
||||||
}}
|
}}
|
||||||
allowEnterKey={false}
|
allowEnterKey={false}
|
||||||
value={(value) => setForm('subtitle', value)}
|
value={(value) => setForm('subtitle', value || '')}
|
||||||
class={styles.subtitleInput}
|
class={styles.subtitleInput}
|
||||||
placeholder={t('Subheader')}
|
placeholder={t('Subheader')}
|
||||||
initialValue={form.subtitle}
|
initialValue={form.subtitle || ''}
|
||||||
maxLength={MAX_HEADER_LIMIT}
|
maxLength={MAX_HEADER_LIMIT}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
@ -70,10 +70,10 @@ export const PublishSettings = (props: Props) => {
|
||||||
return {
|
return {
|
||||||
coverImageUrl: props.form?.coverImageUrl,
|
coverImageUrl: props.form?.coverImageUrl,
|
||||||
mainTopic: props.form?.mainTopic || EMPTY_TOPIC,
|
mainTopic: props.form?.mainTopic || EMPTY_TOPIC,
|
||||||
slug: props.form?.slug,
|
slug: props.form?.slug || '',
|
||||||
title: props.form?.title,
|
title: props.form?.title || '',
|
||||||
subtitle: props.form?.subtitle,
|
subtitle: props.form?.subtitle || '',
|
||||||
description: composeDescription(),
|
description: composeDescription() || '',
|
||||||
selectedTopics: [],
|
selectedTopics: [],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -100,7 +100,7 @@ export const PublishSettings = (props: Props) => {
|
||||||
const handleTopicSelectChange = (newSelectedTopics) => {
|
const handleTopicSelectChange = (newSelectedTopics) => {
|
||||||
if (
|
if (
|
||||||
props.form.selectedTopics.length === 0 ||
|
props.form.selectedTopics.length === 0 ||
|
||||||
newSelectedTopics.every((topic) => topic.id !== props.form.mainTopic.id)
|
newSelectedTopics.every((topic) => topic.id !== props.form.mainTopic?.id)
|
||||||
) {
|
) {
|
||||||
setSettingsForm((prev) => {
|
setSettingsForm((prev) => {
|
||||||
return {
|
return {
|
||||||
|
@ -176,7 +176,7 @@ export const PublishSettings = (props: Props) => {
|
||||||
<div class={styles.mainTopic}>{settingsForm.mainTopic.title}</div>
|
<div class={styles.mainTopic}>{settingsForm.mainTopic.title}</div>
|
||||||
</Show>
|
</Show>
|
||||||
<div class={styles.shoutCardTitle}>{settingsForm.title}</div>
|
<div class={styles.shoutCardTitle}>{settingsForm.title}</div>
|
||||||
<div class={styles.shoutCardSubtitle}>{settingsForm.subtitle}</div>
|
<div class={styles.shoutCardSubtitle}>{settingsForm.subtitle || ''}</div>
|
||||||
<div class={styles.shoutAuthor}>{author()?.name}</div>
|
<div class={styles.shoutAuthor}>{author()?.name}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -203,7 +203,7 @@ export const PublishSettings = (props: Props) => {
|
||||||
variant="bordered"
|
variant="bordered"
|
||||||
fieldName={t('Subheader')}
|
fieldName={t('Subheader')}
|
||||||
placeholder={t('Come up with a subtitle for your story')}
|
placeholder={t('Come up with a subtitle for your story')}
|
||||||
initialValue={settingsForm.subtitle}
|
initialValue={settingsForm.subtitle || ''}
|
||||||
value={(value) => setSettingsForm('subtitle', value)}
|
value={(value) => setSettingsForm('subtitle', value)}
|
||||||
allowEnterKey={false}
|
allowEnterKey={false}
|
||||||
maxLength={100}
|
maxLength={100}
|
||||||
|
|
|
@ -50,7 +50,7 @@ export const ConnectProvider = (props: { children: JSX.Element }) => {
|
||||||
Authorization: token,
|
Authorization: token,
|
||||||
},
|
},
|
||||||
onmessage(event) {
|
onmessage(event) {
|
||||||
const m: SSEMessage = JSON.parse(event.data)
|
const m: SSEMessage = JSON.parse(event.data || '{}')
|
||||||
console.log('[context.connect] Received message:', m)
|
console.log('[context.connect] Received message:', m)
|
||||||
|
|
||||||
// Iterate over all registered handlers and call them
|
// Iterate over all registered handlers and call them
|
||||||
|
|
|
@ -39,7 +39,7 @@ type EditorContextType = {
|
||||||
wordCounter: Accessor<WordCounter>
|
wordCounter: Accessor<WordCounter>
|
||||||
form: ShoutForm
|
form: ShoutForm
|
||||||
formErrors: Record<keyof ShoutForm, string>
|
formErrors: Record<keyof ShoutForm, string>
|
||||||
editorRef: { current: () => Editor }
|
editorRef: { current: () => Editor | null }
|
||||||
saveShout: (form: ShoutForm) => Promise<void>
|
saveShout: (form: ShoutForm) => Promise<void>
|
||||||
saveDraft: (form: ShoutForm) => Promise<void>
|
saveDraft: (form: ShoutForm) => Promise<void>
|
||||||
saveDraftToLocalStorage: (form: ShoutForm) => void
|
saveDraftToLocalStorage: (form: ShoutForm) => void
|
||||||
|
@ -72,7 +72,7 @@ const saveDraftToLocalStorage = (formToSave: ShoutForm) => {
|
||||||
localStorage.setItem(`shout-${formToSave.shoutId}`, JSON.stringify(formToSave))
|
localStorage.setItem(`shout-${formToSave.shoutId}`, JSON.stringify(formToSave))
|
||||||
}
|
}
|
||||||
const getDraftFromLocalStorage = (shoutId: number) => {
|
const getDraftFromLocalStorage = (shoutId: number) => {
|
||||||
return JSON.parse(localStorage.getItem(`shout-${shoutId}`))
|
return JSON.parse(localStorage.getItem(`shout-${shoutId}`) || '{}')
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeDraftFromLocalStorage = (shoutId: number) => {
|
const removeDraftFromLocalStorage = (shoutId: number) => {
|
||||||
|
@ -80,13 +80,19 @@ const removeDraftFromLocalStorage = (shoutId: number) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditorProvider = (props: { children: JSX.Element }) => {
|
export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
const { t } = useLocalize()
|
const localize = useLocalize()
|
||||||
const { page } = useRouter()
|
const { page } = useRouter()
|
||||||
const { showSnackbar } = useSnackbar()
|
const snackbar = useSnackbar()
|
||||||
const [isEditorPanelVisible, setIsEditorPanelVisible] = createSignal<boolean>(false)
|
const [isEditorPanelVisible, setIsEditorPanelVisible] = createSignal<boolean>(false)
|
||||||
const editorRef: { current: () => Editor } = { current: null }
|
const editorRef: { current: () => Editor | null } = { current: () => null }
|
||||||
const [form, setForm] = createStore<ShoutForm>(null)
|
const [form, setForm] = createStore<ShoutForm>({
|
||||||
const [formErrors, setFormErrors] = createStore<Record<keyof ShoutForm, string>>(null)
|
body: '',
|
||||||
|
slug: '',
|
||||||
|
shoutId: 0,
|
||||||
|
title: '',
|
||||||
|
selectedTopics: [],
|
||||||
|
})
|
||||||
|
const [formErrors, setFormErrors] = createStore({} as Record<keyof ShoutForm, string>)
|
||||||
const [wordCounter, setWordCounter] = createSignal<WordCounter>({
|
const [wordCounter, setWordCounter] = createSignal<WordCounter>({
|
||||||
characters: 0,
|
characters: 0,
|
||||||
words: 0,
|
words: 0,
|
||||||
|
@ -95,13 +101,16 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
const countWords = (value) => setWordCounter(value)
|
const countWords = (value) => setWordCounter(value)
|
||||||
const validate = () => {
|
const validate = () => {
|
||||||
if (!form.title) {
|
if (!form.title) {
|
||||||
setFormErrors('title', t('Please, set the article title'))
|
setFormErrors('title', localize?.t('Please, set the article title') || '')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedMedia = JSON.parse(form.media)
|
const parsedMedia = JSON.parse(form.media || '[]')
|
||||||
if (form.layout === 'video' && !parsedMedia[0]) {
|
if (form.layout === 'video' && !parsedMedia[0]) {
|
||||||
showSnackbar({ type: 'error', body: t('Looks like you forgot to upload the video') })
|
snackbar?.showSnackbar({
|
||||||
|
type: 'error',
|
||||||
|
body: localize?.t('Looks like you forgot to upload the video'),
|
||||||
|
})
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +119,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
|
|
||||||
const validateSettings = () => {
|
const validateSettings = () => {
|
||||||
if (form.selectedTopics.length === 0) {
|
if (form.selectedTopics.length === 0) {
|
||||||
setFormErrors('selectedTopics', t('Required'))
|
setFormErrors('selectedTopics', localize?.t('Required') || '')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,6 +127,10 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateShout = async (formToUpdate: ShoutForm, { publish }: { publish: boolean }) => {
|
const updateShout = async (formToUpdate: ShoutForm, { publish }: { publish: boolean }) => {
|
||||||
|
if (!formToUpdate.shoutId) {
|
||||||
|
console.error(formToUpdate)
|
||||||
|
return { error: 'not enought data' }
|
||||||
|
}
|
||||||
return await apiClient.updateArticle({
|
return await apiClient.updateArticle({
|
||||||
shout_id: formToUpdate.shoutId,
|
shout_id: formToUpdate.shoutId,
|
||||||
shout_input: {
|
shout_input: {
|
||||||
|
@ -143,48 +156,61 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
toggleEditorPanel()
|
toggleEditorPanel()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (page().route === 'edit' && !validate()) {
|
if (page()?.route === 'edit' && !validate()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (page().route === 'editSettings' && !validateSettings()) {
|
if (page()?.route === 'editSettings' && !validateSettings()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shout = await updateShout(formToSave, { publish: false })
|
const { shout, error } = await updateShout(formToSave, { publish: false })
|
||||||
|
if (error) {
|
||||||
|
snackbar?.showSnackbar({ type: 'error', body: localize?.t(error) || '' })
|
||||||
|
return
|
||||||
|
}
|
||||||
removeDraftFromLocalStorage(formToSave.shoutId)
|
removeDraftFromLocalStorage(formToSave.shoutId)
|
||||||
|
|
||||||
if (shout.published_at) {
|
if (shout?.published_at) {
|
||||||
openPage(router, 'article', { slug: shout.slug })
|
openPage(router, 'article', { slug: shout.slug })
|
||||||
} else {
|
} else {
|
||||||
openPage(router, 'drafts')
|
openPage(router, 'drafts')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[saveShout]', error)
|
console.error('[saveShout]', error)
|
||||||
showSnackbar({ type: 'error', body: t('Error') })
|
snackbar?.showSnackbar({ type: 'error', body: localize?.t('Error') || '' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveDraft = async (draftForm: ShoutForm) => {
|
const saveDraft = async (draftForm: ShoutForm) => {
|
||||||
await updateShout(draftForm, { publish: false })
|
const { error } = await updateShout(draftForm, { publish: false })
|
||||||
|
if (error) {
|
||||||
|
snackbar?.showSnackbar({ type: 'error', body: localize?.t(error) || '' })
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const publishShout = async (formToPublish: ShoutForm) => {
|
const publishShout = async (formToPublish: ShoutForm) => {
|
||||||
if (isEditorPanelVisible()) {
|
const editorPanelVisible = isEditorPanelVisible()
|
||||||
|
const pageRoute = page()?.route
|
||||||
|
|
||||||
|
if (editorPanelVisible) {
|
||||||
toggleEditorPanel()
|
toggleEditorPanel()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (page().route === 'edit') {
|
if (pageRoute === 'edit') {
|
||||||
if (!validate()) {
|
if (!validate()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateShout(formToPublish, { publish: false })
|
|
||||||
|
|
||||||
const slug = slugify(form.title)
|
const slug = slugify(form.title)
|
||||||
setForm('slug', slug)
|
setForm('slug', slug)
|
||||||
openPage(router, 'editSettings', { shoutId: form.shoutId.toString() })
|
openPage(router, 'editSettings', { shoutId: form.shoutId.toString() })
|
||||||
|
const { error } = await updateShout(formToPublish, { publish: false })
|
||||||
|
if (error) {
|
||||||
|
snackbar?.showSnackbar({ type: 'error', body: localize?.t(error) || '' })
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,20 +219,33 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateShout(formToPublish, { publish: true })
|
const { error } = await updateShout(formToPublish, { publish: true })
|
||||||
|
if (error) {
|
||||||
|
snackbar?.showSnackbar({ type: 'error', body: localize?.t(error) || '' })
|
||||||
|
return
|
||||||
|
}
|
||||||
openPage(router, 'feed')
|
openPage(router, 'feed')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[publishShout]', error)
|
console.error('[publishShout]', error)
|
||||||
showSnackbar({ type: 'error', body: t('Error') })
|
snackbar?.showSnackbar({ type: 'error', body: localize?.t('Error') || '' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const publishShoutById = async (shout_id: number) => {
|
const publishShoutById = async (shout_id: number) => {
|
||||||
|
if (!shout_id) {
|
||||||
|
console.error(`shout_id is ${shout_id}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const newShout = await apiClient.updateArticle({
|
const { shout: newShout, error } = await apiClient.updateArticle({
|
||||||
shout_id,
|
shout_id,
|
||||||
publish: true,
|
publish: true,
|
||||||
})
|
})
|
||||||
|
if (error) {
|
||||||
|
console.error(error)
|
||||||
|
snackbar?.showSnackbar({ type: 'error', body: error })
|
||||||
|
return
|
||||||
|
}
|
||||||
if (newShout) {
|
if (newShout) {
|
||||||
addArticles([newShout])
|
addArticles([newShout])
|
||||||
openPage(router, 'feed')
|
openPage(router, 'feed')
|
||||||
|
@ -215,7 +254,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[publishShoutById]', error)
|
console.error('[publishShoutById]', error)
|
||||||
showSnackbar({ type: 'error', body: t('Error') })
|
snackbar?.showSnackbar({ type: 'error', body: localize?.t('Error') })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,7 +265,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
} catch {
|
} catch {
|
||||||
showSnackbar({ type: 'error', body: t('Error') })
|
snackbar?.showSnackbar({ type: 'error', body: localize?.t('Error') || '' })
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import { createStore, reconcile } from 'solid-js/store'
|
||||||
|
|
||||||
import { apiClient } from '../graphql/client/core'
|
import { apiClient } from '../graphql/client/core'
|
||||||
import { Reaction, ReactionBy, ReactionInput, ReactionKind } from '../graphql/schema/core.gen'
|
import { Reaction, ReactionBy, ReactionInput, ReactionKind } from '../graphql/schema/core.gen'
|
||||||
|
import { useLocalize } from './localize'
|
||||||
|
import { useSnackbar } from './snackbar'
|
||||||
|
|
||||||
type ReactionsContextType = {
|
type ReactionsContextType = {
|
||||||
reactionEntities: Record<number, Reaction>
|
reactionEntities: Record<number, Reaction>
|
||||||
|
@ -19,7 +21,7 @@ type ReactionsContextType = {
|
||||||
}) => Promise<Reaction[]>
|
}) => Promise<Reaction[]>
|
||||||
createReaction: (reaction: ReactionInput) => Promise<void>
|
createReaction: (reaction: ReactionInput) => Promise<void>
|
||||||
updateReaction: (reaction: ReactionInput) => Promise<Reaction>
|
updateReaction: (reaction: ReactionInput) => Promise<Reaction>
|
||||||
deleteReaction: (id: number) => Promise<void>
|
deleteReaction: (id: number) => Promise<{ error: string }>
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReactionsContext = createContext<ReactionsContextType>()
|
const ReactionsContext = createContext<ReactionsContextType>()
|
||||||
|
@ -30,6 +32,8 @@ export function useReactions() {
|
||||||
|
|
||||||
export const ReactionsProvider = (props: { children: JSX.Element }) => {
|
export const ReactionsProvider = (props: { children: JSX.Element }) => {
|
||||||
const [reactionEntities, setReactionEntities] = createStore<Record<number, Reaction>>({})
|
const [reactionEntities, setReactionEntities] = createStore<Record<number, Reaction>>({})
|
||||||
|
const { t } = useLocalize()
|
||||||
|
const { showSnackbar } = useSnackbar()
|
||||||
|
|
||||||
const loadReactionsBy = async ({
|
const loadReactionsBy = async ({
|
||||||
by,
|
by,
|
||||||
|
@ -53,7 +57,8 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const createReaction = async (input: ReactionInput): Promise<void> => {
|
const createReaction = async (input: ReactionInput): Promise<void> => {
|
||||||
const reaction = await apiClient.createReaction(input)
|
const { error, reaction } = await apiClient.createReaction(input)
|
||||||
|
if (error) await showSnackbar({ type: 'error', body: t(error) })
|
||||||
if (!reaction) return
|
if (!reaction) return
|
||||||
const changes = {
|
const changes = {
|
||||||
[reaction.id]: reaction,
|
[reaction.id]: reaction,
|
||||||
|
@ -79,18 +84,22 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
|
||||||
setReactionEntities(changes)
|
setReactionEntities(changes)
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteReaction = async (reaction_id: number): Promise<void> => {
|
const deleteReaction = async (reaction_id: number): Promise<{ error: string; reaction?: string }> => {
|
||||||
if (reaction_id) {
|
if (reaction_id) {
|
||||||
await apiClient.destroyReaction(reaction_id)
|
const result = await apiClient.destroyReaction(reaction_id)
|
||||||
setReactionEntities({
|
if (!result.error) {
|
||||||
[reaction_id]: undefined,
|
setReactionEntities({
|
||||||
})
|
[reaction_id]: undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateReaction = async (input: ReactionInput): Promise<Reaction> => {
|
const updateReaction = async (input: ReactionInput): Promise<Reaction> => {
|
||||||
const reaction = await apiClient.updateReaction(input)
|
const { error, reaction } = await apiClient.updateReaction(input)
|
||||||
setReactionEntities(reaction.id, reaction)
|
if (error) await showSnackbar({ type: 'error', body: t(error) })
|
||||||
|
if (reaction) setReactionEntities(reaction.id, reaction)
|
||||||
return reaction
|
return reaction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ import reactionDestroy from '../mutation/core/reaction-destroy'
|
||||||
import reactionUpdate from '../mutation/core/reaction-update'
|
import reactionUpdate from '../mutation/core/reaction-update'
|
||||||
import unfollowMutation from '../mutation/core/unfollow'
|
import unfollowMutation from '../mutation/core/unfollow'
|
||||||
import shoutLoad from '../query/core/article-load'
|
import shoutLoad from '../query/core/article-load'
|
||||||
|
import getMyShout from '../query/core/article-my'
|
||||||
import shoutsLoadBy from '../query/core/articles-load-by'
|
import shoutsLoadBy from '../query/core/articles-load-by'
|
||||||
import draftsLoad from '../query/core/articles-load-drafts'
|
import draftsLoad from '../query/core/articles-load-drafts'
|
||||||
import myFeed from '../query/core/articles-load-feed'
|
import myFeed from '../query/core/articles-load-feed'
|
||||||
|
@ -41,7 +42,6 @@ import authorFollows from '../query/core/author-follows'
|
||||||
import authorId from '../query/core/author-id'
|
import authorId from '../query/core/author-id'
|
||||||
import authorsAll from '../query/core/authors-all'
|
import authorsAll from '../query/core/authors-all'
|
||||||
import authorsLoadBy from '../query/core/authors-load-by'
|
import authorsLoadBy from '../query/core/authors-load-by'
|
||||||
import mySubscriptions from '../query/core/my-followed'
|
|
||||||
import reactionsLoadBy from '../query/core/reactions-load-by'
|
import reactionsLoadBy from '../query/core/reactions-load-by'
|
||||||
import topicBySlug from '../query/core/topic-by-slug'
|
import topicBySlug from '../query/core/topic-by-slug'
|
||||||
import topicsAll from '../query/core/topics-all'
|
import topicsAll from '../query/core/topics-all'
|
||||||
|
@ -135,7 +135,6 @@ export const apiClient = {
|
||||||
user?: string
|
user?: string
|
||||||
}): Promise<AuthorFollows> => {
|
}): Promise<AuthorFollows> => {
|
||||||
const response = await publicGraphQLClient.query(authorFollows, params).toPromise()
|
const response = await publicGraphQLClient.query(authorFollows, params).toPromise()
|
||||||
console.log('!!! response:', response)
|
|
||||||
return response.data.get_author_follows
|
return response.data.get_author_follows
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -162,12 +161,12 @@ export const apiClient = {
|
||||||
shout_id: number
|
shout_id: number
|
||||||
shout_input?: ShoutInput
|
shout_input?: ShoutInput
|
||||||
publish: boolean
|
publish: boolean
|
||||||
}): Promise<Shout> => {
|
}): Promise<CommonResult> => {
|
||||||
const response = await apiClient.private
|
const response = await apiClient.private
|
||||||
.mutation(updateArticle, { shout_id, shout_input, publish })
|
.mutation(updateArticle, { shout_id, shout_input, publish })
|
||||||
.toPromise()
|
.toPromise()
|
||||||
console.debug('[graphql.client.core] updateArticle:', response.data)
|
console.debug('[graphql.client.core] updateArticle:', response.data)
|
||||||
return response.data.update_shout.shout
|
return response.data.update_shout
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteShout: async (params: MutationDelete_ShoutArgs): Promise<void> => {
|
deleteShout: async (params: MutationDelete_ShoutArgs): Promise<void> => {
|
||||||
|
@ -178,7 +177,7 @@ export const apiClient = {
|
||||||
getDrafts: async (): Promise<Shout[]> => {
|
getDrafts: async (): Promise<Shout[]> => {
|
||||||
const response = await apiClient.private.query(draftsLoad, {}).toPromise()
|
const response = await apiClient.private.query(draftsLoad, {}).toPromise()
|
||||||
console.debug('[graphql.client.core] getDrafts:', response)
|
console.debug('[graphql.client.core] getDrafts:', response)
|
||||||
return response.data.load_shouts_drafts
|
return response.data.get_shouts_drafts
|
||||||
},
|
},
|
||||||
createReaction: async (input: ReactionInput) => {
|
createReaction: async (input: ReactionInput) => {
|
||||||
const response = await apiClient.private.mutation(reactionCreate, { reaction: input }).toPromise()
|
const response = await apiClient.private.mutation(reactionCreate, { reaction: input }).toPromise()
|
||||||
|
@ -188,7 +187,7 @@ export const apiClient = {
|
||||||
destroyReaction: async (reaction_id: number) => {
|
destroyReaction: async (reaction_id: number) => {
|
||||||
const response = await apiClient.private.mutation(reactionDestroy, { reaction_id }).toPromise()
|
const response = await apiClient.private.mutation(reactionDestroy, { reaction_id }).toPromise()
|
||||||
console.debug('[graphql.client.core] destroyReaction:', response)
|
console.debug('[graphql.client.core] destroyReaction:', response)
|
||||||
return response.data.delete_reaction.reaction
|
return response.data.delete_reaction
|
||||||
},
|
},
|
||||||
updateReaction: async (reaction: ReactionInput) => {
|
updateReaction: async (reaction: ReactionInput) => {
|
||||||
const response = await apiClient.private.mutation(reactionUpdate, { reaction }).toPromise()
|
const response = await apiClient.private.mutation(reactionUpdate, { reaction }).toPromise()
|
||||||
|
@ -200,15 +199,18 @@ export const apiClient = {
|
||||||
console.debug('[graphql.client.core] authorsLoadBy:', resp)
|
console.debug('[graphql.client.core] authorsLoadBy:', resp)
|
||||||
return resp.data.load_authors_by
|
return resp.data.load_authors_by
|
||||||
},
|
},
|
||||||
|
|
||||||
getShoutBySlug: async (slug: string) => {
|
getShoutBySlug: async (slug: string) => {
|
||||||
const resp = await publicGraphQLClient.query(shoutLoad, { slug }).toPromise()
|
const resp = await publicGraphQLClient.query(shoutLoad, { slug }).toPromise()
|
||||||
return resp.data.get_shout
|
return resp.data.get_shout
|
||||||
},
|
},
|
||||||
getShoutById: async (shout_id: number) => {
|
|
||||||
const resp = await publicGraphQLClient.query(shoutLoad, { shout_id }).toPromise()
|
getMyShout: async (shout_id: number) => {
|
||||||
|
await apiClient.private
|
||||||
|
const resp = await apiClient.private.query(getMyShout, { shout_id }).toPromise()
|
||||||
if (resp.error) console.error(resp)
|
if (resp.error) console.error(resp)
|
||||||
|
|
||||||
return resp.data.get_shout
|
return resp.data.get_my_shout
|
||||||
},
|
},
|
||||||
|
|
||||||
getShouts: async (options: LoadShoutsOptions) => {
|
getShouts: async (options: LoadShoutsOptions) => {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { gql } from '@urql/core'
|
import { gql } from '@urql/core'
|
||||||
|
|
||||||
export default gql`
|
export default gql`
|
||||||
query LoadShoutQuery($slug: String, $shout_id: Int) {
|
query LoadShoutQuery($slug: String!) {
|
||||||
get_shout(slug: $slug, shout_id: $shout_id) {
|
get_shout(slug: $slug) {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
lead
|
lead
|
||||||
|
|
53
src/graphql/query/core/article-my.ts
Normal file
53
src/graphql/query/core/article-my.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import { gql } from '@urql/core'
|
||||||
|
|
||||||
|
export default gql`
|
||||||
|
query GetMyShout($shout_id: Int!) {
|
||||||
|
get_my_shout(shout_id: $shout_id) {
|
||||||
|
error
|
||||||
|
shout {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
lead
|
||||||
|
description
|
||||||
|
subtitle
|
||||||
|
slug
|
||||||
|
layout
|
||||||
|
cover
|
||||||
|
cover_caption
|
||||||
|
body
|
||||||
|
media
|
||||||
|
updated_by {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
pic
|
||||||
|
created_at
|
||||||
|
}
|
||||||
|
# community
|
||||||
|
main_topic
|
||||||
|
topics {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
body
|
||||||
|
slug
|
||||||
|
stat {
|
||||||
|
shouts
|
||||||
|
authors
|
||||||
|
followers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authors {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
pic
|
||||||
|
created_at
|
||||||
|
}
|
||||||
|
created_at
|
||||||
|
updated_at
|
||||||
|
published_at
|
||||||
|
featured_at
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
|
@ -2,7 +2,7 @@ import { gql } from '@urql/core'
|
||||||
|
|
||||||
export default gql`
|
export default gql`
|
||||||
query LoadDraftsQuery {
|
query LoadDraftsQuery {
|
||||||
load_shouts_drafts {
|
get_shouts_drafts {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
subtitle
|
subtitle
|
||||||
|
@ -35,7 +35,6 @@ export default gql`
|
||||||
featured_at
|
featured_at
|
||||||
stat {
|
stat {
|
||||||
viewed
|
viewed
|
||||||
|
|
||||||
rating
|
rating
|
||||||
commented
|
commented
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ export default gql`
|
||||||
shouts
|
shouts
|
||||||
authors
|
authors
|
||||||
followers
|
followers
|
||||||
|
comments
|
||||||
# viewed
|
# viewed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,22 +7,42 @@ import { useLocalize } from '../context/localize'
|
||||||
import { apiClient } from '../graphql/client/core'
|
import { apiClient } from '../graphql/client/core'
|
||||||
import { Shout } from '../graphql/schema/core.gen'
|
import { Shout } from '../graphql/schema/core.gen'
|
||||||
import { useRouter } from '../stores/router'
|
import { useRouter } from '../stores/router'
|
||||||
|
import { router } from '../stores/router'
|
||||||
|
|
||||||
|
import { redirectPage } from '@nanostores/router'
|
||||||
|
import { useSnackbar } from '../context/snackbar'
|
||||||
import { LayoutType } from './types'
|
import { LayoutType } from './types'
|
||||||
|
|
||||||
const EditView = lazy(() => import('../components/Views/EditView/EditView'))
|
const EditView = lazy(() => import('../components/Views/EditView/EditView'))
|
||||||
|
|
||||||
export const EditPage = () => {
|
export const EditPage = () => {
|
||||||
const { page } = useRouter()
|
const { page } = useRouter()
|
||||||
|
const snackbar = useSnackbar()
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
|
||||||
const shoutId = createMemo(() => Number((page().params as Record<'shoutId', string>).shoutId))
|
|
||||||
|
|
||||||
const [shout, setShout] = createSignal<Shout>(null)
|
const [shout, setShout] = createSignal<Shout>(null)
|
||||||
|
const loadMyShout = async (shout_id: number) => {
|
||||||
|
if (shout_id) {
|
||||||
|
const { shout: loadedShout, error } = await apiClient.getMyShout(shout_id)
|
||||||
|
console.log(loadedShout)
|
||||||
|
if (error) {
|
||||||
|
await snackbar?.showSnackbar({ type: 'error', body: t('This content is not published yet') })
|
||||||
|
redirectPage(router, 'drafts')
|
||||||
|
} else {
|
||||||
|
setShout(loadedShout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const loadedShout = await apiClient.getShoutById(shoutId())
|
const shout_id = window.location.pathname.split('/').pop()
|
||||||
setShout(loadedShout)
|
if (shout_id) {
|
||||||
|
try {
|
||||||
|
await loadMyShout(parseInt(shout_id, 10))
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const title = createMemo(() => {
|
const title = createMemo(() => {
|
||||||
|
|
|
@ -7,7 +7,7 @@ export const byCreated = (a: Shout | Reaction, b: Shout | Reaction) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const byPublished = (a: Shout, b: Shout) => {
|
export const byPublished = (a: Shout, b: Shout) => {
|
||||||
return a.published_at - b.published_at
|
return (a?.published_at || 0) - (b?.published_at || 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const byLength = (
|
export const byLength = (
|
||||||
|
|
Loading…
Reference in New Issue
Block a user