mainpage-loadmore-hide

This commit is contained in:
Untone 2024-07-18 12:22:28 +03:00
parent ab05a9e539
commit 7573c6334c
10 changed files with 112 additions and 72 deletions

View File

@ -8,6 +8,8 @@ import sassDts from 'vite-plugin-sass-dts'
const isVercel = Boolean(process?.env.VERCEL)
const isBun = Boolean(process.env.BUN)
console.info(`[app.config] build for ${isVercel ? 'vercel' : isBun? 'bun' : 'node'}!`)
const polyfillOptions = {
include: ['path', 'stream', 'util'],
exclude: ['http'],

View File

@ -1,9 +1,8 @@
import { A, useLocation } from '@solidjs/router'
import { clsx } from 'clsx'
import { For, Match, Show, Switch, createEffect, createMemo, createSignal, on, onMount } from 'solid-js'
import { For, Match, Show, Switch, createEffect, createMemo, createSignal, on } from 'solid-js'
import { Loading } from '~/components/_shared/Loading'
import { useAuthors } from '~/context/authors'
import { useFeed } from '~/context/feed'
import { useFollowing } from '~/context/following'
import { useGraphQL } from '~/context/graphql'
import { useLocalize } from '~/context/localize'
@ -28,7 +27,6 @@ type AuthorViewProps = {
selectedTab: string
shouts?: Shout[]
author?: Author
topics?: Topic[]
}
export const PRERENDERED_ARTICLES_COUNT = 12
@ -40,7 +38,6 @@ export const AuthorView = (props: AuthorViewProps) => {
const loc = useLocation()
const { session } = useSession()
const { query } = useGraphQL()
const { sortedFeed } = useFeed()
const { loadAuthor, authorsEntities } = useAuthors()
const { followers: myFollowers, follows: myFollows } = useFollowing()
@ -55,7 +52,7 @@ export const AuthorView = (props: AuthorViewProps) => {
// derivatives
const me = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
const pages = createMemo<Shout[][]>(() =>
paginate(sortedFeed(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
paginate((props.shouts || []).slice(1), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
)
// 1 // проверяет не собственный ли это профиль, иначе - загружает
@ -107,12 +104,11 @@ export const AuthorView = (props: AuthorViewProps) => {
// event handlers
let bioContainerRef: HTMLDivElement
let bioWrapperRef: HTMLDivElement
const checkBioHeight = () => {
if (bioContainerRef) {
const showExpand = bioContainerRef.offsetHeight > bioWrapperRef.offsetHeight
setShowExpandBioControl(showExpand)
console.debug('[AuthorView] mounted, show expand bio container:', showExpand)
}
const checkBioHeight = (bio = bioWrapperRef) => {
if (!bio) return
const showExpand = bioContainerRef.offsetHeight > bio.offsetHeight
setShowExpandBioControl(showExpand)
console.debug('[AuthorView] mounted, show expand bio container:', showExpand)
}
const handleDeleteComment = (id: number) => {
@ -120,7 +116,8 @@ export const AuthorView = (props: AuthorViewProps) => {
}
// on load
onMount(checkBioHeight)
createEffect(on(() => bioContainerRef, checkBioHeight))
createEffect(on(() => props.selectedTab, (tab) => tab && console.log('[views.Author] profile tab switched')))
return (
<div class={styles.authorPage}>
@ -227,18 +224,18 @@ export const AuthorView = (props: AuthorViewProps) => {
</div>
</Show>
<Show when={sortedFeed().length > 0}>
<Row1 article={sortedFeed()[0]} noauthor={true} nodate={true} />
<Show when={Array.isArray(props.shouts) && props.shouts.length > 0 && props.shouts[0]}>
<Row1 article={props.shouts?.[0] as Shout} noauthor={true} nodate={true} />
<Show when={sortedFeed().length > 1}>
<Show when={props.shouts && props.shouts.length > 1}>
<Switch>
<Match when={sortedFeed().length === 2}>
<Row2 articles={sortedFeed()} isEqual={true} noauthor={true} nodate={true} />
<Match when={props.shouts && props.shouts.length === 2}>
<Row2 articles={props.shouts as Shout[]} isEqual={true} noauthor={true} nodate={true} />
</Match>
<Match when={sortedFeed().length === 3}>
<Row3 articles={sortedFeed()} noauthor={true} nodate={true} />
<Match when={props.shouts && props.shouts.length === 3}>
<Row3 articles={props.shouts as Shout[]} noauthor={true} nodate={true} />
</Match>
<Match when={sortedFeed().length > 3}>
<Match when={props.shouts && props.shouts.length > 3}>
<For each={pages()}>
{(page) => (
<>

View File

@ -39,6 +39,12 @@ export type FeedProps = {
order?: '' | 'likes' | 'hot'
}
const PERIODS = {
'day': 24 * 60 * 60,
'month': 30 * 24 * 60 * 60,
'year': 365 * 24 * 60 * 60
}
export const FeedView = (props: FeedProps) => {
const { t } = useLocalize()
const loc = useLocation()
@ -59,9 +65,6 @@ export const FeedView = (props: FeedProps) => {
const { topAuthors } = useAuthors()
const [topComments, setTopComments] = createSignal<Reaction[]>([])
const [searchParams, changeSearchParams] = useSearchParams<FeedSearchParams>()
const asOption = (o: string) => ({ value: o, title: t(o) })
const asOptions = (opts: string[]) => opts.map(asOption)
const currentPeriod = createMemo(() => asOption(searchParams?.period || ''))
const loadTopComments = async () => {
const comments = await loadReactionsBy({ by: { comment: true }, limit: 50 })
setTopComments(comments.sort(byCreated).reverse())
@ -94,6 +97,13 @@ export const FeedView = (props: FeedProps) => {
setShareData(shared)
}
const asOption = (o: string) => {
const value = Math.floor(Date.now()/1000) - PERIODS[o as keyof typeof PERIODS]
return { value, title: t(o) }
}
const asOptions = (opts: string[]) => opts.map(asOption)
const currentPeriod = createMemo(() => asOption(searchParams?.period || ''))
return (
<div class="feed">
<div class="row">

View File

@ -89,7 +89,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
if (data.entity === 'reaction' && authorized()) {
console.info('[context.notifications] event', data)
loadNotificationsGrouped({
after: after() || Date.now(),
after: after() || now,
limit: Math.max(PAGE_SIZE, loadedNotificationsCount())
})
}
@ -108,14 +108,14 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
const markSeenAll = async () => {
if (authorized()) {
const _resp = await mutation(markSeenAfterMutation, { after: after() }).toPromise()
await loadNotificationsGrouped({ after: after() || Date.now(), limit: loadedNotificationsCount() })
await loadNotificationsGrouped({ after: after() || now, limit: loadedNotificationsCount() })
}
}
const markSeen = async (notification_id: number) => {
if (authorized()) {
await mutation(markSeenMutation, { notification_id }).toPromise()
await loadNotificationsGrouped({ after: after() || Date.now(), limit: loadedNotificationsCount() })
await loadNotificationsGrouped({ after: after() || now, limit: loadedNotificationsCount() })
}
}

View File

@ -58,6 +58,10 @@ export const loadShouts = (options: LoadShoutsOptions) => {
}
export const loadReactions = (options: QueryLoad_Reactions_ByArgs) => {
if (!options.by) {
console.debug(options)
throw new Error('[api] wrong loadReactions call')
}
const kind = options.by?.comment ? 'comments' : options.by?.rating ? 'votes' : 'reactions'
const allorone = options.by?.shout ? `shout-${options.by.shout}` : 'all'
const page = `${options.offset || 0}-${(options?.limit || 0) + (options.offset || 0)}`

View File

@ -45,6 +45,7 @@
"Artworks": "Артворки",
"Audio": "Аудио",
"Author": "Автор",
"author profile was not found": "не удалось найти профиль автора",
"Authors": "Авторы",
"Autotypograph": "Автотипограф",
"Back": "Назад",

View File

@ -109,7 +109,7 @@ export default function HomePage(props: RouteSectionProps<HomeViewProps>) {
return (
<PageLayout withPadding={true} title={t('Discours')} key="home">
<LoadMoreWrapper loadFunction={loadMoreFeatured} pageSize={SHOUTS_PER_PAGE}>
<LoadMoreWrapper loadFunction={loadMoreFeatured} pageSize={SHOUTS_PER_PAGE} hidden={!featuredFeed()}>
<HomeView
featuredShouts={featuredFeed() || (shouts() as Shout[])}
topMonthShouts={topMonthFeed() as Shout[]}

View File

@ -39,7 +39,7 @@ type SlugPageProps = {
export default (props: RouteSectionProps<SlugPageProps>) => {
if (props.params.slug.startsWith('@')) {
console.debug('[slug] starts with @, render as author page')
console.debug('[routes] [slug]/[...tab] starts with @, render as author page')
const patchedProps = {
...props,
params: {
@ -51,7 +51,7 @@ export default (props: RouteSectionProps<SlugPageProps>) => {
}
if (props.params.slug.startsWith('!')) {
console.debug('[slug] starts with !, render as topic page')
console.debug('[routes] [slug]/[...tab] starts with !, render as topic page')
const patchedProps = {
...props,
params: {
@ -74,7 +74,7 @@ export default (props: RouteSectionProps<SlugPageProps>) => {
await loadGAScript(gaIdentity)
initGA(gaIdentity)
} catch (error) {
console.warn('Failed to connect Google Analytics:', error)
console.warn('[routes] [slug]/[...tab] Failed to connect Google Analytics:', error)
}
}
})

View File

@ -1,5 +1,5 @@
import { RouteSectionProps, createAsync } from '@solidjs/router'
import { ErrorBoundary, createEffect, createMemo } from 'solid-js'
import { RouteSectionProps } from '@solidjs/router'
import { ErrorBoundary, createEffect, createMemo, createSignal, on } from 'solid-js'
import { AuthorView } from '~/components/Views/Author'
import { FourOuFourView } from '~/components/Views/FourOuFour'
import { LoadMoreItems, LoadMoreWrapper } from '~/components/_shared/LoadMoreWrapper'
@ -13,6 +13,9 @@ import {
Author,
LoadShoutsOptions,
QueryLoad_Authors_ByArgs,
QueryLoad_Reactions_ByArgs,
Reaction,
ReactionKind,
Shout,
Topic
} from '~/graphql/schema/core.gen'
@ -25,6 +28,16 @@ const fetchAuthorShouts = async (slug: string, offset?: number) => {
return await shoutsLoader()
}
const fetchAuthorComments = async (slug: string, offset?: number) => {
const opts: QueryLoad_Reactions_ByArgs = {
by: { comment: true, author: slug },
limit: SHOUTS_PER_PAGE,
offset
}
const shoutsLoader = loadReactions(opts)
return await shoutsLoader()
}
const fetchAllTopics = async () => {
const topicsFetcher = loadTopics()
return await topicsFetcher()
@ -51,18 +64,8 @@ export type AuthorPageProps = { articles?: Shout[]; author?: Author; topics?: To
export default function AuthorPage(props: RouteSectionProps<AuthorPageProps>) {
const { addAuthor, authorsEntities } = useAuthors()
const articles = createAsync(
async () => props.data.articles || (await fetchAuthorShouts(props.params.slug)) || []
)
const author = createAsync(async () => {
const loadedBefore = authorsEntities()[props.params.slug]
if (loadedBefore) return loadedBefore
const [author, setAuthor] = createSignal<Author | undefined>(undefined)
const a = props.data.author || (await fetchAuthor(props.params.slug))
a && addAuthor(a)
return a
})
const topics = createAsync(async () => props.data.topics || (await fetchAllTopics()))
const { t } = useLocalize()
const title = createMemo(() => `${author()?.name || ''}`)
@ -83,28 +86,44 @@ export default function AuthorPage(props: RouteSectionProps<AuthorPageProps>) {
: getImageUrl('production/image/logo_image.png')
)
const selectedTab = createMemo(() => (props.params.tab in ['comments', 'about'] ? props.params.tab : ''))
const { addReactions } = useReactions()
const loadMoreComments = async () => {
const commentsFetcher = loadReactions({
by: { comment: true, created_by: author()?.id }
})
const ccc = await commentsFetcher()
ccc && addReactions(ccc)
return ccc as LoadMoreItems
}
// author comments
const { addReactions, reactionEntities } = useReactions()
const commentsByAuthor = createMemo(() =>
Object.values(reactionEntities).filter(
(r: Reaction) => r.kind === ReactionKind.Comment && r.created_by.id === author()?.id
)
)
// author shouts
const { addFeed, feedByAuthor } = useFeed()
const loadMoreAuthorShouts = async () => {
const slug = author()?.slug
const offset = feedByAuthor()[props.params.slug].length
const shoutsFetcher = loadShouts({
filters: { author: slug },
offset,
limit: SHOUTS_PER_PAGE
})
const sss = await shoutsFetcher()
sss && addFeed(sss)
return sss as LoadMoreItems
const shoutsByAuthor = createMemo(() => feedByAuthor()[props.params.slug])
createEffect(
on(
[() => props.params.slug || '', author],
async ([slug, profile]) => {
if (!profile) {
const loadedAuthor = authorsEntities()[slug] || (await fetchAuthor(slug))
if (loadedAuthor) {
addAuthor(loadedAuthor)
setAuthor(loadedAuthor)
}
}
},
{ defer: true }
)
)
const loadAuthorDataMore = async (offset = 0) => {
if (props.params.tab === 'comments') {
const commentsOffset = commentsByAuthor().length
const loadedComments = await fetchAuthorComments(props.params.slug, commentsOffset)
loadedComments && addReactions(loadedComments)
return (loadedComments || []) as LoadMoreItems
}
const shoutsOffset = shoutsByAuthor().length
const loadedShouts = await fetchAuthorShouts(props.params.slug, shoutsOffset)
loadedShouts && addFeed(loadedShouts)
return (loadedShouts || []) as LoadMoreItems
}
return (
@ -118,16 +137,15 @@ export default function AuthorPage(props: RouteSectionProps<AuthorPageProps>) {
>
<ReactionsProvider>
<LoadMoreWrapper
loadFunction={selectedTab() === 'comments' ? loadMoreComments : loadMoreAuthorShouts}
loadFunction={loadAuthorDataMore}
pageSize={SHOUTS_PER_PAGE}
hidden={selectedTab() !== '' || selectedTab() !== 'comments'}
hidden={!props.params.tab || props.params.tab !== 'comments'}
>
<AuthorView
author={author() as Author}
selectedTab={selectedTab()}
selectedTab={props.params.tab}
authorSlug={props.params.slug}
shouts={feedByAuthor()[props.params.slug] || (articles() as Shout[])}
topics={topics()}
shouts={shoutsByAuthor()}
/>
</LoadMoreWrapper>
</ReactionsProvider>

View File

@ -7,6 +7,7 @@ import { Icon } from '~/components/_shared/Icon'
import { PageLayout } from '~/components/_shared/PageLayout'
import { useGraphQL } from '~/context/graphql'
import { useLocalize } from '~/context/localize'
import { useSnackbar } from '~/context/ui'
import createShoutMutation from '~/graphql/mutation/core/article-create'
import styles from '~/styles/Create.module.scss'
import { LayoutType } from '~/types/common'
@ -14,11 +15,18 @@ import { LayoutType } from '~/types/common'
export default () => {
const { t } = useLocalize()
const client = useGraphQL()
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) {
const shout = result.data.create_shout
console.debug(result)
const {shout, error} = result.data.create_shout
if (error) showSnackbar({
body: `${t('Error')}: ${t(error)}`,
type: 'error'
})
if (shout?.id) navigate(`/edit/${shout.id}`)
}
}
@ -34,8 +42,8 @@ export default () => {
<ul class={clsx('nodash', styles.list)}>
<For each={['Article', 'Literature', 'Image', 'Audio', 'Video']}>
{(layout: string) => (
<li>
<div class={styles.link} onClick={() => handleCreate(layout.toLowerCase() as LayoutType)}>
<li onClick={() => handleCreate(layout.toLowerCase() as LayoutType)}>
<div class={styles.link}>
<Icon name={`create-${layout.toLowerCase()}`} class={styles.icon} />
<div>{t(layout)}</div>
</div>