mainpage-loadmore-hide
This commit is contained in:
parent
ab05a9e539
commit
7573c6334c
|
@ -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'],
|
||||
|
|
|
@ -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) => (
|
||||
<>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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() })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)}`
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
"Artworks": "Артворки",
|
||||
"Audio": "Аудио",
|
||||
"Author": "Автор",
|
||||
"author profile was not found": "не удалось найти профиль автора",
|
||||
"Authors": "Авторы",
|
||||
"Autotypograph": "Автотипограф",
|
||||
"Back": "Назад",
|
||||
|
|
|
@ -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[]}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue
Block a user