drafts-view-refactoring+fix
Some checks failed
deploy / Update templates on Mailgun (push) Waiting to run
deploy / test (push) Has been cancelled

This commit is contained in:
Untone 2024-10-02 21:29:32 +03:00
parent 217c027044
commit 6f26e09bef
26 changed files with 182 additions and 156 deletions

View File

@ -3,7 +3,7 @@ import { Component, Show, createEffect, createMemo } from 'solid-js'
import { Dynamic } from 'solid-js/web'
import { useLocalize } from '~/context/localize'
import { AuthModalSource, useUI } from '~/context/ui'
import { ModalSource, useUI } from '~/context/ui'
import { isMobile } from '~/lib/mediaQuery'
import { ChangePasswordForm } from './ChangePasswordForm'
import { EmailConfirm } from './EmailConfirm'
@ -25,7 +25,7 @@ export type AuthModalMode =
export type AuthModalSearchParams = {
mode: AuthModalMode
source?: AuthModalSource
source?: ModalSource
token?: string
}

View File

@ -1,3 +1,7 @@
.draft {
margin-bottom: 56px;
}
.created {
@include font-size(1.2rem);

View File

@ -1,15 +1,13 @@
import { A } from '@solidjs/router'
import { clsx } from 'clsx'
import { useLocalize } from '~/context/localize'
import { useSnackbar, useUI } from '~/context/ui'
import type { Shout } from '~/graphql/schema/core.gen'
import { Icon } from '../_shared/Icon'
import { A } from '@solidjs/router'
import styles from './Draft.module.scss'
type Props = {
class?: string
shout: Shout
onPublish: (shout: Shout) => void
onDelete: (shout: Shout) => void
@ -46,7 +44,7 @@ export const Draft = (props: Props) => {
}
return (
<div class={clsx(props.class)}>
<div class={styles.draft}>
<div class={styles.created}>
<Icon name="pencil-outline" class={styles.icon} />{' '}
{formatDate(new Date(props.shout.created_at * 1000), { hour: '2-digit', minute: '2-digit' })}

View File

@ -0,0 +1,67 @@
import { useNavigate } from '@solidjs/router'
import clsx from 'clsx'
import { For } from 'solid-js'
import { useEditorContext } from '~/context/editor'
import { useLocalize } from '~/context/localize'
import { useSession } from '~/context/session'
import { useSnackbar } from '~/context/ui'
import createShoutMutation from '~/graphql/mutation/core/article-create'
import { LayoutType } from '~/types/common'
import { Button } from '../_shared/Button'
import { Icon } from '../_shared/Icon'
import styles from './LayoutSelector.module.scss'
export const LayoutSelector = () => {
const { t } = useLocalize()
const { client } = useSession()
const { saveDraftToLocalStorage } = useEditorContext()
const { showSnackbar } = useSnackbar()
const navigate = useNavigate()
const handleCreate = async (layout: LayoutType) => {
console.debug('[routes : edit/new] handling create click...')
const result = await client()
?.mutation(createShoutMutation, { shout: { layout: layout } })
.toPromise()
if (result) {
console.debug(result)
const { shout, error } = result.data.create_shout
if (error) {
showSnackbar({
body: `${t('Error')}: ${t(error)}`,
type: 'error'
})
return
}
if (shout?.id) {
saveDraftToLocalStorage({
shoutId: shout.id,
selectedTopics: shout.topics,
slug: shout.slug,
title: '',
body: ''
})
navigate(`/edit/${shout.id}`)
}
}
}
return (
<article class={clsx('wide-container', 'container--static-page', styles.Create)}>
<h1>{t('Choose a post type')}</h1>
<ul class={clsx('nodash', styles.list)}>
<For each={['Article', 'Literature', 'Image', 'Audio', 'Video']}>
{(layout: string) => (
<li onClick={() => handleCreate(layout.toLowerCase() as LayoutType)}>
<div class={styles.link}>
<Icon name={`create-${layout.toLowerCase()}`} class={styles.icon} />
<div>{t(layout)}</div>
</div>
</li>
)}
</For>
</ul>
<Button value={t('Back')} onClick={() => window?.history.back()} />
</article>
)
}

View File

@ -12,7 +12,7 @@ import { Modal } from '../../_shared/Modal'
import { Menu } from './Menu'
import type { MenuItem } from './Menu/Menu'
import styles from './EditorFloatingMenu.module.scss'
import styles from '../EditorFloatingMenu/EditorFloatingMenu.module.scss'
type FloatingMenuProps = {
editor: Editor

View File

@ -0,0 +1,54 @@
import { Show, createEffect, createSignal, on } from 'solid-js'
import { useAuthors } from '~/context/authors'
import { useLocalize } from '~/context/localize'
import { useTopics } from '~/context/topics'
import { loadShouts } from '~/graphql/api/public'
import { Author, Shout, Topic } from '~/graphql/schema/core.gen'
import { capitalize } from '~/utils/capitalize'
import { Icon } from '../_shared/Icon'
import Group from './Group'
import styles from './RandomTopicSwiper.module.scss'
export const RandomTopicSwiper = () => {
const { t } = useLocalize()
const { randomTopic } = useTopics()
const { addAuthors } = useAuthors()
const [randomTopicArticles, setRandomTopicArticles] = createSignal<Shout[]>([])
createEffect(
on(
() => randomTopic(), // NOTE: triggs once
async (topic?: Topic) => {
if (topic) {
const shoutsByTopicLoader = loadShouts({
filters: { topic: topic.slug, featured: true },
limit: 5,
offset: 0
})
const shouts = await shoutsByTopicLoader()
setRandomTopicArticles(shouts || [])
shouts?.forEach((s: Shout) => addAuthors((s?.authors || []) as Author[]))
}
},
{ defer: true }
)
)
return (
<Show when={Boolean(randomTopic())}>
<Group
articles={randomTopicArticles() || []}
header={
<div class={styles.randomTopicHeaderContainer}>
<div class={styles.randomTopicHeader}>{capitalize(randomTopic()?.title || '', true)}</div>
<div>
<a class={styles.randomTopicHeaderLink} href={`/topic/${randomTopic()?.slug || ''}`}>
{t('All articles')} <Icon class={styles.icon} name="arrow-right" />
</a>
</div>
</div>
}
/>
</Show>
)
}

View File

@ -8,7 +8,6 @@ import { SharePopup, getShareUrl } from '../Article/SharePopup'
import { AuthModal } from '../AuthModal'
import { SearchModal } from '../SearchModal/SearchModal'
import { Snackbar } from '../Snackbar/Snackbar'
import { RandomTopics } from '../TopicsNav/TopicsNav'
import { ConfirmModal } from '../_shared/ConfirmModal'
import { Icon } from '../_shared/Icon'
import { Modal } from '../_shared/Modal'
@ -16,6 +15,7 @@ import { Newsletter } from '../_shared/Newsletter'
import styles from './Header.module.scss'
import { HeaderAuth } from './HeaderAuth'
import { Link } from './HeaderLink'
import { RandomTopics } from './TopicsNav'
type Props = {
title?: string

View File

@ -7,6 +7,7 @@ import { useTopics } from '~/context/topics'
import type { Topic } from '~/graphql/schema/core.gen'
import { notLatin } from '~/intl/chars'
import { getRandomItemsFromArray } from '~/utils/random'
import styles from './TopicsNav.module.scss'
export const RandomTopics = () => {

View File

@ -1 +0,0 @@
export { TopicsNav } from './TopicsNav'

View File

@ -1,16 +1,26 @@
import { useNavigate } from '@solidjs/router'
import { clsx } from 'clsx'
import { For, Show, createSignal } from 'solid-js'
import { Client } from '@urql/core'
import { For, Show, createEffect, createSignal, on, onMount } from 'solid-js'
import { Draft } from '~/components/Draft'
import { useEditorContext } from '~/context/editor'
import { useLocalize } from '~/context/localize'
import { useSession } from '~/context/session'
import getDraftsQuery from '~/graphql/query/core/articles-load-drafts'
import { Shout } from '~/graphql/schema/core.gen'
import styles from './DraftsView.module.scss'
export const DraftsView = (props: { drafts: Shout[] }) => {
const [drafts, setDrafts] = createSignal<Shout[]>(props.drafts || [])
const fetchDrafts = async (client: Client) => {
const resp = await client?.query(getDraftsQuery, {}).toPromise()
const result = resp?.data?.get_shouts_drafts || []
if (resp.error || result.error) console.error(resp.error || result.error)
return result.shouts as Shout[]
}
export const DraftsView = (props: { drafts?: Shout[] }) => {
const { client, requireAuthentication} = useSession()
const navigate = useNavigate()
const { publishShoutById, deleteShout } = useEditorContext()
const [drafts, setDrafts] = createSignal<Shout[]>(props.drafts || [])
const handleDraftDelete = async (shout: Shout) => {
const success = await deleteShout(shout.id)
if (success) {
@ -23,10 +33,20 @@ export const DraftsView = (props: { drafts: Shout[] }) => {
setTimeout(() => navigate('/feed'), 2000)
}
onMount(() => {
requireAuthentication(async () => {
const result = await fetchDrafts(client() as Client)
console.debug('fetchDrafts result: ', result)
if (result) {
setDrafts(result as Shout[])
}
}, 'edit')
})
const { t } = useLocalize()
return (
<div class={clsx(styles.DraftsView)}>
<div>
<div class="wide-container">
<div class="row offset-md-5">
<h2>{t('Drafts')}</h2>
@ -37,12 +57,7 @@ export const DraftsView = (props: { drafts: Shout[] }) => {
<div class="col-md-19 col-lg-18 col-xl-16 offset-md-5">
<For each={ddd()}>
{(draft) => (
<Draft
class={styles.draft}
shout={draft}
onDelete={handleDraftDelete}
onPublish={handleDraftPublish}
/>
<Draft shout={draft} onDelete={handleDraftDelete} onPublish={handleDraftPublish} />
)}
</For>
</div>

View File

@ -1,7 +0,0 @@
.DraftsView {
display: block;
}
.draft {
margin-bottom: 56px;
}

View File

@ -1 +0,0 @@
export { DraftsView } from './DraftsView'

View File

@ -1,25 +1,21 @@
import { For, Show, createEffect, createMemo, createSignal, on } from 'solid-js'
import { For, Show, createMemo, onMount } from 'solid-js'
import { useAuthors } from '~/context/authors'
import { SHOUTS_PER_PAGE } from '~/context/feed'
import { useLocalize } from '~/context/localize'
import { useTopics } from '~/context/topics'
import { loadShouts } from '~/graphql/api/public'
import { Author, Shout, Topic } from '~/graphql/schema/core.gen'
import { capitalize } from '~/utils/capitalize'
import { paginate } from '~/utils/paginate'
import Banner from '../Discours/Banner'
import Hero from '../Discours/Hero'
import { Beside } from '../Feed/Beside'
import Group from '../Feed/Group'
import { RandomTopicSwiper } from '../Feed/RandomTopicSwiper'
import { Row1 } from '../Feed/Row1'
import { Row2 } from '../Feed/Row2'
import { Row3 } from '../Feed/Row3'
import { Row5 } from '../Feed/Row5'
import RowShort from '../Feed/RowShort'
import { TopicsNav } from '../TopicsNav'
import { Icon } from '../_shared/Icon'
import { TopicsNav } from '../HeaderNav/TopicsNav'
import { ArticleCardSwiper } from '../_shared/SolidSwiper/ArticleCardSwiper'
import styles from './Home.module.scss'
export const RANDOM_TOPICS_COUNT = 12
export const RANDOM_TOPIC_SHOUTS_COUNT = 7
@ -38,28 +34,11 @@ export interface HomeViewProps {
export const HomeView = (props: HomeViewProps) => {
const { t } = useLocalize()
const { topAuthors, addAuthors } = useAuthors()
const { topTopics, randomTopic } = useTopics()
const [randomTopicArticles, setRandomTopicArticles] = createSignal<Shout[]>([])
createEffect(
on(
() => randomTopic(),
async (topic?: Topic) => {
if (topic) {
const shoutsByTopicLoader = loadShouts({
filters: { topic: topic.slug, featured: true },
limit: 5,
offset: 0
})
const shouts = await shoutsByTopicLoader()
setRandomTopicArticles(shouts || [])
shouts?.forEach((s: Shout) => addAuthors((s?.authors || []) as Author[]))
props.featuredShouts?.forEach((s: Shout) => addAuthors((s?.authors || []) as Author[]))
props.topRatedShouts?.forEach((s: Shout) => addAuthors((s?.authors || []) as Author[]))
}
},
{ defer: true }
)
)
const { topTopics } = useTopics()
onMount(() => {
props.featuredShouts?.forEach((s: Shout) => addAuthors((s?.authors || []) as Author[]))
props.topRatedShouts?.forEach((s: Shout) => addAuthors((s?.authors || []) as Author[]))
})
const pages = createMemo<Shout[][]>(() =>
paginate(props.featuredShouts || [], SHOUTS_PER_PAGE + CLIENT_LOAD_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
@ -99,21 +78,8 @@ export const HomeView = (props: HomeViewProps) => {
header={<h2>{t('Top commented')}</h2>}
nodate={true}
/>
<Show when={Boolean(randomTopic())}>
<Group
articles={randomTopicArticles() || []}
header={
<div class={styles.randomTopicHeaderContainer}>
<div class={styles.randomTopicHeader}>{capitalize(randomTopic()?.title || '', true)}</div>
<div>
<a class={styles.randomTopicHeaderLink} href={`/topic/${randomTopic()?.slug || ''}`}>
{t('All articles')} <Icon class={styles.icon} name="arrow-right" />
</a>
</div>
</div>
}
/>
</Show>
<RandomTopicSwiper />
<ArticleCardSwiper title={t('Favorite')} slides={props.topRatedShouts} />

View File

@ -2,6 +2,7 @@ import { useNavigate } from '@solidjs/router'
import { clsx } from 'clsx'
import { Show, createEffect, createSignal, lazy, onMount } from 'solid-js'
import { createStore } from 'solid-js/store'
import { UploadModalContent } from '~/components/Upload/UploadModalContent/UploadModalContent'
import { Button } from '~/components/_shared/Button'
import { Icon } from '~/components/_shared/Icon'
import { Image } from '~/components/_shared/Image'
@ -13,9 +14,8 @@ import { useSnackbar, useUI } from '~/context/ui'
import { Topic } from '~/graphql/schema/core.gen'
import { UploadedFile } from '~/types/upload'
import { Modal } from '../../_shared/Modal'
import { TopicSelect } from './TopicSelect'
import { TopicSelect } from '~/components/TopicSelect/TopicSelect'
import { UploadModalContent } from '~/components/Upload/UploadModalContent/UploadModalContent'
import stylesBeside from '../../Feed/Beside.module.scss'
import styles from './PublishSettings.module.scss'

View File

@ -25,7 +25,7 @@ import {
onMount,
useContext
} from 'solid-js'
import { type AuthModalSource, useSnackbar, useUI } from '~/context/ui'
import { type ModalSource, useSnackbar, useUI } from '~/context/ui'
import { graphqlClientCreate } from '~/graphql/client'
import { authApiUrl, authorizerClientId, authorizerRedirectUrl, coreApiUrl } from '../config'
import { useLocalize } from './localize'
@ -45,7 +45,7 @@ export type SessionContextType = {
setSession: (token: AuthToken) => void
requireAuthentication: (
callback: (() => Promise<void>) | (() => void),
modalSource: AuthModalSource
modalSource: ModalSource
) => void
signUp: (params: SignupInput) => Promise<boolean>
signIn: (params: LoginInput) => Promise<boolean>
@ -265,7 +265,7 @@ export const SessionProvider = (props: {
* @param callback - The function to execute after authentication.
* @param modalSource - The source of the authentication modal.
*/
const requireAuthentication = (callback: () => void, modalSource: AuthModalSource) => {
const requireAuthentication = (callback: () => void, modalSource: ModalSource) => {
setAuthCallback(() => callback)
if (!session()) {
loadSession()

View File

@ -70,7 +70,7 @@ export const SnackbarProvider = (props: { children: JSX.Element }) => {
return <SnackbarContext.Provider value={value}>{props.children}</SnackbarContext.Provider>
}
export type AuthModalSource =
export type ModalSource =
| 'discussions'
| 'vote'
| 'subscribe'
@ -78,6 +78,7 @@ export type AuthModalSource =
| 'follow'
| 'create'
| 'authguard'
| 'edit'
export type ModalType =
| 'auth'
@ -128,7 +129,7 @@ type ConfirmMessage = {
type UIContextType = {
modal: Accessor<ModalType | null>
showModal: (m: ModalType, source?: AuthModalSource) => void
showModal: (m: ModalType, source?: ModalSource) => void
hideModal: () => void
confirmMessage: Accessor<ConfirmMessage>
showConfirm: (message?: ConfirmMessage) => Promise<boolean>
@ -163,7 +164,7 @@ export const UIProvider = (props: { children: JSX.Element }) => {
hideModal()
}
const showModal = (modalType: ModalType, modalSource?: AuthModalSource) => {
const showModal = (modalType: ModalType, modalSource?: ModalSource) => {
// console.log('[context.ui] showModal()', modalType)
if (modalSource) {
setSearchParams({ source: modalSource })

View File

@ -1,28 +1,15 @@
import { createAsync } from '@solidjs/router'
import { Client } from '@urql/core'
import { AuthGuard } from '~/components/AuthGuard'
import { DraftsView } from '~/components/Views/DraftsView'
import { PageLayout } from '~/components/_shared/PageLayout'
import { useLocalize } from '~/context/localize'
import { useSession } from '~/context/session'
import getDraftsQuery from '~/graphql/query/core/articles-load-drafts'
import { Shout } from '~/graphql/schema/core.gen'
const fetchDrafts = async (client: Client) => {
const resp = await client?.query(getDraftsQuery, {}).toPromise()
const result = resp?.data?.load_drafts || []
return result as Shout[]
}
export default () => {
const { t } = useLocalize()
const { client } = useSession()
const drafts = createAsync(async () => client() && (await fetchDrafts(client() as Client)))
return (
<PageLayout title={`${t('Discours')} :: ${t('Drafts')}`}>
<AuthGuard>
<DraftsView drafts={drafts() || []} />
<DraftsView />
</AuthGuard>
</PageLayout>
)

View File

@ -1,53 +1,10 @@
import { useNavigate } from '@solidjs/router'
import { clsx } from 'clsx'
import { For } from 'solid-js'
import { AuthGuard } from '~/components/AuthGuard'
import { Button } from '~/components/_shared/Button'
import { Icon } from '~/components/_shared/Icon'
import { LayoutSelector } from '~/components/Draft/LayoutSelector'
import { PageLayout } from '~/components/_shared/PageLayout'
import { useEditorContext } from '~/context/editor'
import { useLocalize } from '~/context/localize'
import { useSession } from '~/context/session'
import { useSnackbar } from '~/context/ui'
import createShoutMutation from '~/graphql/mutation/core/article-create'
import { LayoutType } from '~/types/common'
import styles from '~/styles/Create.module.scss'
export default () => {
const { t } = useLocalize()
const { client } = useSession()
const { saveDraftToLocalStorage } = useEditorContext()
const { showSnackbar } = useSnackbar()
const navigate = useNavigate()
const handleCreate = async (layout: LayoutType) => {
console.debug('[routes : edit/new] handling create click...')
const result = await client()
?.mutation(createShoutMutation, { shout: { layout: layout } })
.toPromise()
if (result) {
console.debug(result)
const { shout, error } = result.data.create_shout
if (error) {
showSnackbar({
body: `${t('Error')}: ${t(error)}`,
type: 'error'
})
return
}
if (shout?.id) {
saveDraftToLocalStorage({
shoutId: shout.id,
selectedTopics: shout.topics,
slug: shout.slug,
title: '',
body: ''
})
navigate(`/edit/${shout.id}`)
}
}
}
return (
<PageLayout
title={`${t('Discours')} :: ${t('Choose a post type')}`}
@ -55,22 +12,7 @@ export default () => {
desc={t('Participate in the Discours: share information, join the editorial team')}
>
<AuthGuard>
<article class={clsx('wide-container', 'container--static-page', styles.Create)}>
<h1>{t('Choose a post type')}</h1>
<ul class={clsx('nodash', styles.list)}>
<For each={['Article', 'Literature', 'Image', 'Audio', 'Video']}>
{(layout: string) => (
<li onClick={() => handleCreate(layout.toLowerCase() as LayoutType)}>
<div class={styles.link}>
<Icon name={`create-${layout.toLowerCase()}`} class={styles.icon} />
<div>{t(layout)}</div>
</div>
</li>
)}
</For>
</ul>
<Button value={t('Back')} onClick={() => window?.history.back()} />
</article>
<LayoutSelector />
</AuthGuard>
</PageLayout>
)

View File

@ -1,6 +1,6 @@
import { Params, RouteSectionProps, createAsync } from '@solidjs/router'
import { Show, createEffect, createSignal, on } from 'solid-js'
import { TopicsNav } from '~/components/TopicsNav'
import { TopicsNav } from '~/components/HeaderNav/TopicsNav'
import { Expo } from '~/components/Views/Expo'
import ExpoNav from '~/components/Views/Expo/ExpoNav'
import { LoadMoreItems, LoadMoreWrapper } from '~/components/_shared/LoadMoreWrapper'