Merge pull request #447 from Discours/hotfix/editor-permission

use-session in editor
This commit is contained in:
Tony 2024-05-06 17:33:04 +03:00 committed by GitHub
commit 8f09d6fc54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 207 additions and 201 deletions

View File

@ -99,4 +99,4 @@
}
}
}
}
}

View File

@ -40,16 +40,12 @@ import { InboxPage } from '../pages/inbox.page'
import { HomePage } from '../pages/index.page'
import { ProfileSecurityPage } from '../pages/profile/profileSecurity.page'
import { ProfileSettingsPage } from '../pages/profile/profileSettings.page'
//TODO: ProfileSubscriptionsPage - garbage code?
import { ProfileSubscriptionsPage } from '../pages/profile/profileSubscriptions.page'
import { SearchPage } from '../pages/search.page'
import { TopicPage } from '../pages/topic.page'
import { ROUTES, useRouter } from '../stores/router'
import { MODALS, showModal } from '../stores/ui'
// TODO: lazy load
// const SomePage = lazy(() => import('./Pages/SomePage'))
const pagesMap: Record<keyof typeof ROUTES, Component<PageProps>> = {
author: AuthorPage,
authorComments: AuthorPage,

View File

@ -75,7 +75,7 @@ export const FullArticle = (props: Props) => {
const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false)
const [isActionPopupActive, setIsActionPopupActive] = createSignal(false)
const { t, formatDate, lang } = useLocalize()
const { author, session, isAuthenticated, requireAuthentication } = useSession()
const { author, session, requireAuthentication } = useSession()
const formattedDate = createMemo(() => formatDate(new Date(props.article.published_at * 1000)))
@ -561,7 +561,7 @@ export const FullArticle = (props: Props) => {
/>
</div>
<Show when={isAuthenticated() && !canEdit()}>
<Show when={author()?.id && !canEdit()}>
<div class={styles.help}>
<button class="button">{t('Cooperate')}</button>
</div>

View File

@ -12,7 +12,7 @@ type Props = {
}
export const AuthGuard = (props: Props) => {
const { isAuthenticated, isSessionLoaded } = useSession()
const { author, isSessionLoaded } = useSession()
const { changeSearchParams } = useRouter<RootSearchParams & AuthModalSearchParams>()
createEffect(() => {
@ -20,7 +20,7 @@ export const AuthGuard = (props: Props) => {
return
}
if (isSessionLoaded()) {
if (isAuthenticated()) {
if (author()?.id) {
hideModal()
} else {
changeSearchParams(
@ -37,5 +37,5 @@ export const AuthGuard = (props: Props) => {
}
})
return <Show when={(isSessionLoaded() && isAuthenticated()) || props.disabled}>{props.children}</Show>
return <Show when={(isSessionLoaded() && author()?.id) || props.disabled}>{props.children}</Show>
}

View File

@ -119,6 +119,9 @@ export const AuthorBadge = (props: Props) => {
<Show when={props.author?.stat.shouts > 0}>
<div>{t('PublicationsWithCount', { count: props.author.stat?.shouts ?? 0 })}</div>
</Show>
<Show when={props.author?.stat.comments > 0}>
<div>{t('CommentsWithCount', { count: props.author.stat?.comments ?? 0 })}</div>
</Show>
<Show when={props.author?.stat.followers > 0}>
<div>{t('FollowersWithCount', { count: props.author.stat?.followers ?? 0 })}</div>
</Show>

View File

@ -1,7 +1,7 @@
import { useLocalize } from '../../context/localize'
import { useRouter } from '../../stores/router'
import { showModal } from '../../stores/ui'
import { AuthModalSearchParams } from '../Nav/AuthModal/types'
import type { AuthModalSearchParams } from '../Nav/AuthModal/types'
import styles from './Hero.module.scss'

View File

@ -7,6 +7,7 @@ import { composeMediaItems } from '../../../utils/composeMediaItems'
import { AudioPlayer } from '../../Article/AudioPlayer'
import { DropArea } from '../../_shared/DropArea'
// import { Buffer } from 'node:buffer'
import styles from './AudioUploader.module.scss'
window.Buffer = Buffer

View File

@ -30,8 +30,10 @@ const embedData = (data) => {
// biome-ignore lint/style/useForOf: <explanation>
for (let i = 0; i < attributes.length; i++) {
const attribute = attributes[i]
result[attribute.name] = attribute.value
const attribute = attributes.item(i)
if (attribute) {
result[attribute.name] = attribute.value
}
}
return result

View File

@ -23,8 +23,16 @@ type Props = {
export const Panel = (props: Props) => {
const { t } = useLocalize()
const { isEditorPanelVisible, wordCounter, editorRef, form, toggleEditorPanel, saveShout, publishShout } =
useEditorContext()
const {
isEditorPanelVisible,
wordCounter,
editorRef,
form,
toggleEditorPanel,
saveShout,
saveDraft,
publishShout,
} = useEditorContext()
const containerRef: { current: HTMLElement } = { current: null }
const [isShortcutsVisible, setIsShortcutsVisible] = createSignal(false)
@ -43,7 +51,12 @@ export const Panel = (props: Props) => {
})
const handleSaveClick = () => {
saveShout(form)
const hasTopics = form.selectedTopics?.length > 0
if (hasTopics) {
saveShout(form)
} else {
saveDraft(form)
}
}
const html = useEditorHTML(() => editorRef.current())

View File

@ -31,7 +31,7 @@ export const LoginForm = () => {
const [isSubmitting, setIsSubmitting] = createSignal(false)
const [password, setPassword] = createSignal('')
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
// FIXME: use signal or remove
const [_isLinkSent, setIsLinkSent] = createSignal(false)
const authFormRef: { current: HTMLFormElement } = { current: null }
const { showSnackbar } = useSnackbar()

View File

@ -32,6 +32,7 @@ export const RegisterForm = () => {
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
const { t } = useLocalize()
const { signUp, isRegistered, resendVerifyEmail } = useSession()
// FIXME: use submit error data or remove signal
const [_submitError, setSubmitError] = createSignal('')
const [fullName, setFullName] = createSignal('')
const [password, setPassword] = createSignal('')

View File

@ -59,7 +59,7 @@ export const Header = (props: Props) => {
const [isTopicsVisible, setIsTopicsVisible] = createSignal(false)
const [isZineVisible, setIsZineVisible] = createSignal(false)
const [isFeedVisible, setIsFeedVisible] = createSignal(false)
const { isAuthenticated } = useSession()
const { session } = useSession()
const toggleFixed = () => setFixed(!fixed())
@ -335,7 +335,7 @@ export const Header = (props: Props) => {
<Show when={props.title}>
<div
class={clsx(styles.articleControls, 'col-auto', {
[styles.articleControlsAuthorized]: isAuthenticated(),
[styles.articleControlsAuthorized]: session()?.user?.id,
})}
>
<SharePopup

View File

@ -32,14 +32,14 @@ const MD_WIDTH_BREAKPOINT = 992
export const HeaderAuth = (props: Props) => {
const { t } = useLocalize()
const { page } = useRouter()
const { session, author, isAuthenticated, isSessionLoaded } = useSession()
const { session, author, isSessionLoaded } = useSession()
const { unreadNotificationsCount, showNotificationsPanel } = useNotifications()
const { form, toggleEditorPanel, saveShout, publishShout } = useEditorContext()
const { form, toggleEditorPanel, saveShout, saveDraft, publishShout } = useEditorContext()
const handleBellIconClick = (event: Event) => {
event.preventDefault()
if (!isAuthenticated()) {
if (!author()?.id) {
showModal('auth')
return
}
@ -48,19 +48,22 @@ export const HeaderAuth = (props: Props) => {
}
const isEditorPage = createMemo(() => page().route === 'edit' || page().route === 'editSettings')
const isNotificationsVisible = createMemo(() => isAuthenticated() && !isEditorPage())
const isSaveButtonVisible = createMemo(() => isAuthenticated() && isEditorPage())
const isNotificationsVisible = createMemo(() => author()?.id && !isEditorPage())
const isSaveButtonVisible = createMemo(() => author()?.id && isEditorPage())
const isCreatePostButtonVisible = createMemo(() => !isEditorPage())
const isAuthenticatedControlsVisible = createMemo(
() => isAuthenticated() && session()?.user?.email_verified,
)
const isAuthenticatedControlsVisible = createMemo(() => author()?.id && session()?.user?.email_verified)
const handleBurgerButtonClick = () => {
toggleEditorPanel()
}
const _handleSaveButtonClick = () => {
saveShout(form)
const handleSaveClick = () => {
const hasTopics = form.selectedTopics?.length > 0
if (hasTopics) {
saveShout(form)
} else {
saveDraft(form)
}
}
const [width, setWidth] = createSignal(0)
@ -106,14 +109,8 @@ export const HeaderAuth = (props: Props) => {
<Show when={isSessionLoaded()} keyed={true}>
<div class={clsx('col-auto col-lg-7', styles.usernav)}>
<div class={styles.userControl}>
<Show when={isCreatePostButtonVisible() && isAuthenticated()}>
<div
class={clsx(
styles.userControlItem,
styles.userControlItemVerbose,
styles.userControlItemCreate,
)}
>
<Show when={isCreatePostButtonVisible() && author()?.id}>
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
<a href={getPagePath(router, 'create')}>
<span class={styles.textLabel}>{t('Create post')}</span>
<Icon name="pencil-outline" class={styles.icon} />
@ -220,14 +217,8 @@ export const HeaderAuth = (props: Props) => {
</div>
</Show>
<Show when={isCreatePostButtonVisible() && !isAuthenticated()}>
<div
class={clsx(
styles.userControlItem,
styles.userControlItemVerbose,
styles.userControlItemCreate,
)}
>
<Show when={isCreatePostButtonVisible() && !author()?.id}>
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
<a href={getPagePath(router, 'create')}>
<span class={styles.textLabel}>{t('Create post')}</span>
<Icon name="pencil-outline" class={styles.icon} />
@ -239,7 +230,7 @@ export const HeaderAuth = (props: Props) => {
<Show
when={isAuthenticatedControlsVisible()}
fallback={
<Show when={!isAuthenticated()}>
<Show when={!author()?.id}>
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose, 'loginbtn')}>
<a href="?m=auth&mode=login">
<span class={styles.textLabel}>{t('Enter')}</span>
@ -250,25 +241,31 @@ export const HeaderAuth = (props: Props) => {
</Show>
}
>
<Show when={!isSaveButtonVisible()}>
<div
class={clsx(
styles.userControlItem,
// styles.userControlItemInbox
)}
>
<a href={getPagePath(router, 'inbox')}>
<div classList={{ entered: page().path === '/inbox' }}>
<Icon name="inbox-white" class={styles.icon} />
<Icon name="inbox-white-hover" class={clsx(styles.icon, styles.iconHover)} />
</div>
</a>
<Show
when={isSaveButtonVisible()}
fallback={
<div class={clsx(styles.userControlItem)}>
<a href={getPagePath(router, 'inbox')}>
<div classList={{ entered: page().path === '/inbox' }}>
<Icon name="inbox-white" class={styles.icon} />
<Icon name="inbox-white-hover" class={clsx(styles.icon, styles.iconHover)} />
</div>
</a>
</div>
}
>
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
<button onClick={handleSaveClick}>
<span class={styles.textLabel}>{t('Save')}</span>
<Icon name="save" class={styles.icon} />
<Icon name="save" class={clsx(styles.icon, styles.iconHover)} />
</button>
</div>
</Show>
</Show>
</div>
<Show when={isAuthenticated()}>
<Show when={author()?.id}>
<ProfilePopup
onVisibilityChange={(isVisible) => {
props.setIsProfilePopupVisible(isVisible)

View File

@ -46,7 +46,7 @@ const isEarlier = (date: Date) => {
export const NotificationsPanel = (props: Props) => {
const [isLoading, setIsLoading] = createSignal(false)
const { isAuthenticated } = useSession()
const { author } = useSession()
const { t } = useLocalize()
const {
after,
@ -150,16 +150,13 @@ export const NotificationsPanel = (props: Props) => {
})
createEffect(
on(
() => isAuthenticated(),
async () => {
if (isAuthenticated()) {
setIsLoading(true)
await loadNextPage()
setIsLoading(false)
}
},
),
on(author, async (a) => {
if (a?.id) {
setIsLoading(true)
await loadNextPage()
setIsLoading(false)
}
}),
)
return (

View File

@ -49,11 +49,9 @@ export const TopicCard = (props: TopicProps) => {
const handleFollowClick = () => {
requireAuthentication(() => {
if (isSubscribed()) {
unfollow(FollowingEntity.Topic, props.topic.slug)
} else {
follow(FollowingEntity.Topic, props.topic.slug)
}
isSubscribed()
? unfollow(FollowingEntity.Topic, props.topic.slug)
: follow(FollowingEntity.Topic, props.topic.slug)
}, 'subscribe')
}

View File

@ -42,7 +42,6 @@ export const AuthorView = (props: Props) => {
const { followers: myFollowers } = useFollowing()
const { session } = useSession()
const { sortedArticles } = useArticlesStore({ shouts: props.shouts })
// const { authorEntities } = useAuthorsStore({ authors: [props.author] })
const { page: getPage, searchParams } = useRouter()
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
const [isBioExpanded, setIsBioExpanded] = createSignal(false)
@ -87,7 +86,7 @@ export const AuthorView = (props: Props) => {
setFollowing([...(authors || []), ...(topics || [])])
setFollowers(followersResult || [])
console.info('[components.Author] data loaded')
console.debug('[components.Author] following data loaded', subscriptionsResult)
} catch (error) {
console.error('[components.Author] fetch error', error)
}

View File

@ -1,6 +1,6 @@
import { openPage } from '@nanostores/router'
import { clsx } from 'clsx'
import { For, Show, createEffect, createSignal } from 'solid-js'
import { For, Show, createEffect, createSignal, on } from 'solid-js'
import { useEditorContext } from '../../../context/editor'
import { useSession } from '../../../context/session'
@ -9,22 +9,24 @@ import { Shout } from '../../../graphql/schema/core.gen'
import { router } from '../../../stores/router'
import { Draft } from '../../Draft'
import { Loading } from '../../_shared/Loading'
import styles from './DraftsView.module.scss'
export const DraftsView = () => {
const { isAuthenticated, isSessionLoaded } = useSession()
const { session } = useSession()
const [drafts, setDrafts] = createSignal<Shout[]>([])
const loadDrafts = async () => {
if (apiClient.private) {
const loadedDrafts = await apiClient.getDrafts()
setDrafts(loadedDrafts.reverse() || [])
}
}
createEffect(() => {
if (isSessionLoaded()) loadDrafts()
})
createEffect(
on(
() => session(),
async (s) => {
if (s) {
const loadedDrafts = await apiClient.getDrafts()
setDrafts(loadedDrafts.reverse() || [])
}
},
),
)
const { publishShoutById, deleteShout } = useEditorContext()
@ -44,22 +46,20 @@ export const DraftsView = () => {
return (
<div class={clsx(styles.DraftsView)}>
<Show when={isSessionLoaded()}>
<Show when={session()?.user?.id} fallback={<Loading />}>
<div class="wide-container">
<div class="row">
<div class="col-md-19 col-lg-18 col-xl-16 offset-md-5">
<Show when={isAuthenticated()} fallback="Давайте авторизуемся">
<For each={drafts()}>
{(draft) => (
<Draft
class={styles.draft}
shout={draft}
onDelete={handleDraftDelete}
onPublish={handleDraftPublish}
/>
)}
</For>
</Show>
<For each={drafts()}>
{(draft) => (
<Draft
class={styles.draft}
shout={draft}
onDelete={handleDraftDelete}
onPublish={handleDraftPublish}
/>
)}
</For>
</div>
</div>
</div>

View File

@ -2,6 +2,7 @@ import { clsx } from 'clsx'
import deepEqual from 'fast-deep-equal'
import { Accessor, Show, createMemo, createSignal, lazy, onCleanup, onMount } from 'solid-js'
import { createStore } from 'solid-js/store'
import { throttle } from 'throttle-debounce'
import { ShoutForm, useEditorContext } from '../../../context/editor'
import { useLocalize } from '../../../context/localize'
@ -41,7 +42,9 @@ export const EMPTY_TOPIC: Topic = {
slug: '',
}
const THROTTLING_INTERVAL = 2000
const AUTO_SAVE_INTERVAL = 5000
const AUTO_SAVE_DELAY = 5000
const handleScrollTopButtonClick = (e) => {
e.preventDefault()
window.scrollTo({
@ -65,12 +68,14 @@ export const EditView = (props: Props) => {
} = useEditorContext()
const shoutTopics = props.shout.topics || []
// TODO: проверить сохранение черновика в local storage (не работает)
const draft = getDraftFromLocalStorage(props.shout.id)
if (draft) {
setForm(Object.keys(draft).length !== 0 ? draft : { shoutId: props.shout.id })
const draftForm = Object.keys(draft).length !== 0 ? draft : { shoutId: props.shout.id }
setForm(draftForm)
console.debug('draft from localstorage: ', draftForm)
} else {
setForm({
const draftForm = {
slug: props.shout.slug,
shoutId: props.shout.id,
title: props.shout.title,
@ -83,7 +88,9 @@ export const EditView = (props: Props) => {
coverImageUrl: props.shout.cover,
media: props.shout.media,
layout: props.shout.layout,
})
}
setForm(draftForm)
console.debug('draft from props data: ', draftForm)
}
const subtitleInput: { current: HTMLTextAreaElement } = { current: null }
@ -106,9 +113,6 @@ export const EditView = (props: Props) => {
onCleanup(() => {
window.removeEventListener('scroll', handleScroll)
})
})
onMount(() => {
// eslint-disable-next-line unicorn/consistent-function-scoping
const handleBeforeUnload = (event) => {
if (!deepEqual(prevForm, form)) {
@ -180,42 +184,39 @@ export const EditView = (props: Props) => {
let autoSaveTimeOutId: number | string | NodeJS.Timeout
//TODO: add throttle
const autoSave = async () => {
const hasChanges = !deepEqual(form, prevForm)
const hasTopic = Boolean(form.mainTopic)
if (hasChanges || hasTopic) {
console.debug('saving draft', form)
setSaving(true)
saveDraftToLocalStorage(form)
await saveDraft(form)
setPrevForm(clone(form))
setTimeout(() => setSaving(false), AUTO_SAVE_DELAY)
}
}
// Throttle the autoSave function
const throttledAutoSave = throttle(THROTTLING_INTERVAL, autoSave)
const autoSaveRecursive = () => {
autoSaveTimeOutId = setTimeout(async () => {
const hasChanges = !deepEqual(form, prevForm)
if (hasChanges) {
setSaving(true)
if (props.shout?.published_at) {
saveDraftToLocalStorage(form)
} else {
await saveDraft(form)
}
setPrevForm(clone(form))
setTimeout(() => {
setSaving(false)
}, 2000)
}
autoSaveTimeOutId = setTimeout(() => {
throttledAutoSave()
autoSaveRecursive()
}, AUTO_SAVE_INTERVAL)
}
const stopAutoSave = () => {
clearTimeout(autoSaveTimeOutId)
}
onMount(() => {
autoSaveRecursive()
})
onCleanup(() => {
stopAutoSave()
onCleanup(() => clearTimeout(autoSaveTimeOutId))
})
const showSubtitleInput = () => {
setIsSubtitleVisible(true)
subtitleInput.current.focus()
}
const showLeadInput = () => {
setIsLeadVisible(true)
}

View File

@ -49,7 +49,7 @@ type VisibilityItem = {
}
type FeedSearchParams = {
by: 'publish_date' | 'likes' | 'comments'
by: 'publish_date' | 'likes' | 'last_comment'
period: FeedPeriod
visibility: VisibilityMode
}
@ -258,10 +258,10 @@ export const FeedView = (props: Props) => {
</li>
<li
class={clsx({
'view-switcher__item--selected': searchParams().by === 'comments',
'view-switcher__item--selected': searchParams().by === 'last_comment',
})}
>
<span class="link" onClick={() => changeSearchParams({ by: 'comments' })}>
<span class="link" onClick={() => changeSearchParams({ by: 'last_comment' })}>
{t('Most commented')}
</span>
</li>

View File

@ -40,11 +40,11 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0)
const [totalNotificationsCount, setTotalNotificationsCount] = createSignal(0)
const [notificationEntities, setNotificationEntities] = createStore<Record<string, NotificationGroup>>({})
const { isAuthenticated } = useSession()
const { author } = useSession()
const { addHandler } = useConnect()
const loadNotificationsGrouped = async (options: { after: number; limit?: number; offset?: number }) => {
if (isAuthenticated() && notifierClient?.private) {
if (author()?.id && notifierClient?.private) {
const notificationsResult = await notifierClient.getNotifications(options)
const groups = notificationsResult?.notifications || []
const total = notificationsResult?.total || 0
@ -74,7 +74,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
onMount(() => {
addHandler((data: SSEMessage) => {
if (data.entity === 'reaction' && isAuthenticated()) {
if (data.entity === 'reaction' && author()?.id) {
console.info('[context.notifications] event', data)
loadNotificationsGrouped({ after: after(), limit: Math.max(PAGE_SIZE, loadedNotificationsCount()) })
}
@ -91,14 +91,14 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
}
const markSeenAll = async () => {
if (isAuthenticated() && notifierClient.private) {
if (author()?.id && notifierClient.private) {
await notifierClient.markSeenAfter({ after: after() })
await loadNotificationsGrouped({ after: after(), limit: loadedNotificationsCount() })
}
}
const markSeen = async (notification_id: number) => {
if (isAuthenticated() && notifierClient.private) {
if (author()?.id && notifierClient.private) {
await notifierClient.markSeen(notification_id)
await loadNotificationsGrouped({ after: after(), limit: loadedNotificationsCount() })
}

View File

@ -48,7 +48,6 @@ export type SessionContextType = {
author: Resource<Author | null>
authError: Accessor<string>
isSessionLoaded: Accessor<boolean>
isAuthenticated: Accessor<boolean>
loadSession: () => AuthToken | Promise<AuthToken>
setSession: (token: AuthToken | null) => void // setSession
loadAuthor: (info?: unknown) => Author | Promise<Author>
@ -271,12 +270,9 @@ export const SessionProvider = (props: {
// callback state updater
createEffect(
on(
() => props.onStateChangeCallback,
() => {
props.onStateChangeCallback(session())
},
),
on([() => props.onStateChangeCallback, session], ([_, ses]) => {
ses?.user?.id && props.onStateChangeCallback(ses)
}),
)
const [authCallback, setAuthCallback] = createSignal<() => void>(noop)
@ -378,9 +374,6 @@ export const SessionProvider = (props: {
console.warn(error)
}
}
const isAuthenticated = createMemo(() => Boolean(author()))
const actions = {
loadSession,
requireAuthentication,
@ -405,7 +398,6 @@ export const SessionProvider = (props: {
isSessionLoaded,
author,
...actions,
isAuthenticated,
resendVerifyEmail,
}

View File

@ -1,9 +1,10 @@
import { Show, Suspense, createMemo, createSignal, lazy, onMount } from 'solid-js'
import { Show, Suspense, createEffect, createMemo, createSignal, lazy, on, onMount } from 'solid-js'
import { AuthGuard } from '../components/AuthGuard'
import { Loading } from '../components/_shared/Loading'
import { PageLayout } from '../components/_shared/PageLayout'
import { useLocalize } from '../context/localize'
import { useSession } from '../context/session'
import { apiClient } from '../graphql/client/core'
import { Shout } from '../graphql/schema/core.gen'
import { router } from '../stores/router'
@ -14,67 +15,70 @@ import { LayoutType } from './types'
const EditView = lazy(() => import('../components/Views/EditView/EditView'))
export const EditPage = () => {
const snackbar = useSnackbar()
const { t } = useLocalize()
const getContentTypeTitle = (layout: LayoutType) => {
switch (layout) {
case 'audio':
return 'Publish Album'
case 'image':
return 'Create gallery'
case 'video':
return 'Create video'
case 'literature':
return 'New literary work'
default:
return 'Write an article'
}
}
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)
}
}
export const EditPage = () => {
const { t } = useLocalize()
const { session } = useSession()
const snackbar = useSnackbar()
const fail = async (error: string) => {
console.error(error)
await snackbar?.showSnackbar({ type: 'error', body: t(error) })
redirectPage(router, 'drafts')
}
onMount(async () => {
const shout_id = window.location.pathname.split('/').pop()
if (shout_id) {
try {
await loadMyShout(Number.parseInt(shout_id, 10))
} catch (e) {
console.error(e)
}
}
const [shoutId, setShoutId] = createSignal<number>(0)
const [shout, setShout] = createSignal<Shout>()
onMount(() => {
const shoutId = window.location.pathname.split('/').pop()
const shoutIdFromUrl = Number.parseInt(shoutId ?? '0', 10)
console.debug(`editing shout ${shoutIdFromUrl}`)
if (shoutIdFromUrl) setShoutId(shoutIdFromUrl)
})
createEffect(
on([session, shout, shoutId], async ([ses, sh, shid]) => {
if (ses?.user && !sh && shid) {
const { shout: loadedShout, error } = await apiClient.getMyShout(shid)
if (error) {
fail(error)
} else {
setShout(loadedShout)
}
}
}),
)
const title = createMemo(() => {
if (!shout()) {
return t('Create post')
}
switch (shout().layout as LayoutType) {
case 'audio': {
return t('Publish Album')
}
case 'image': {
return t('Create gallery')
}
case 'video': {
return t('Create video')
}
case 'literature': {
return t('New literary work')
}
default: {
return t('Write an article')
}
}
return t(getContentTypeTitle(shout()?.layout as LayoutType))
})
return (
<PageLayout title={title()}>
<AuthGuard>
<Show when={shout()}>
<Suspense fallback={<Loading />}>
<EditView shout={shout()} />
</Suspense>
</Show>
<Suspense fallback={<Loading />}>
<Show when={shout()} fallback={<Loading />}>
<EditView shout={shout() as Shout} />
</Show>
</Suspense>
</AuthGuard>
</PageLayout>
)

View File

@ -69,6 +69,7 @@ const checkOpenOnClient = (link: HTMLAnchorElement, event) => {
)
}
// TODO: use scrollToHash or remove
const _scrollToHash = (hash: string) => {
let selector = hash

View File

@ -6,7 +6,7 @@ import { Author, QueryLoad_Authors_ByArgs } from '../../graphql/schema/core.gen'
export type AuthorsSortBy = 'shouts' | 'name' | 'followers'
type SortedAuthorsSetter = (prev: Author[]) => Author[]
// FIXME: use signal or remove
const [_sortAllBy, setSortAllBy] = createSignal<AuthorsSortBy>('name')
export const setAuthorsSort = (sortBy: AuthorsSortBy) => setSortAllBy(sortBy)

View File

@ -2,6 +2,7 @@ import { RANDOM_TOPICS_COUNT } from '../components/Views/Home'
import { Topic } from '../graphql/schema/core.gen'
export const getRandomTopicsFromArray = (topics: Topic[], count: number = RANDOM_TOPICS_COUNT): Topic[] => {
if (!Array.isArray(topics)) return []
const shuffledTopics = [...topics].sort(() => 0.5 - Math.random())
return shuffledTopics.slice(0, count)
}