author-feed-debug

This commit is contained in:
Untone 2024-07-22 14:24:36 +03:00
parent 1a9529d9fc
commit 01e7dec615
12 changed files with 224 additions and 109 deletions

8
.vscode/launch.json vendored
View File

@ -1,16 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Brave against localhost",
"name": "Launch browser against localhost",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"url": "https://localhost:3000",
"webRoot": "${workspaceFolder}/src",
"sourceMaps": true,
"trace": true,
"runtimeExecutable": "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
"trace": true
}
]
}

View File

@ -1,12 +1,10 @@
import { clsx } from 'clsx'
import { For, Show, createMemo, createSignal, lazy, onMount } from 'solid-js'
import { useFeed } from '~/context/feed'
import { useLocalize } from '~/context/localize'
import { useReactions } from '~/context/reactions'
import { COMMENTS_PER_PAGE, useReactions } from '~/context/reactions'
import { useSession } from '~/context/session'
import {
QueryLoad_Reactions_ByArgs,
Reaction,
ReactionKind,
ReactionSort,
@ -26,12 +24,11 @@ const SimplifiedEditor = lazy(() => import('../Editor/SimplifiedEditor'))
type Props = {
shout: Shout
}
const COMMENTS_PER_PAGE = 50
export const CommentsTree = (props: Props) => {
const { session } = useSession()
const { t } = useLocalize()
const { reactionEntities, createReaction, loadReactionsBy, addReactions } = useReactions()
const { reactionEntities, createReaction, loadShoutComments } = useReactions()
const { seen } = useFeed()
const [commentsOrder, setCommentsOrder] = createSignal<ReactionSort>(ReactionSort.Newest)
const [onlyNew, setOnlyNew] = createSignal(false)
@ -83,13 +80,7 @@ export const CommentsTree = (props: Props) => {
setCommentsLoading(true)
const next = pagination() + 1
const offset = next * COMMENTS_PER_PAGE
const opts: QueryLoad_Reactions_ByArgs = {
by: { comment: true, shout: props.shout.slug },
limit: COMMENTS_PER_PAGE,
offset
}
const rrr = await loadReactionsBy(opts)
rrr && addReactions(rrr)
const rrr = await loadShoutComments(props.shout.id, COMMENTS_PER_PAGE, offset)
rrr && setPagination(next)
setCommentsLoading(false)
return rrr as LoadMoreItems

View File

@ -3,10 +3,10 @@ import { clsx } from 'clsx'
import { Show, createEffect, createMemo, createSignal, on } from 'solid-js'
import { byCreated } from '~/lib/sort'
import { useLocalize } from '../../context/localize'
import { useReactions } from '../../context/reactions'
import { RATINGS_PER_PAGE, useReactions } from '../../context/reactions'
import { useSession } from '../../context/session'
import { useSnackbar } from '../../context/ui'
import { QueryLoad_Reactions_ByArgs, Reaction, ReactionKind, Shout } from '../../graphql/schema/core.gen'
import { Reaction, ReactionKind, Shout } from '../../graphql/schema/core.gen'
import { Icon } from '../_shared/Icon'
import { InlineLoader } from '../_shared/InlineLoader'
import { LoadMoreItems, LoadMoreWrapper } from '../_shared/LoadMoreWrapper'
@ -26,8 +26,7 @@ export const RatingControl = (props: RatingControlProps) => {
const [_, changeSearchParams] = useSearchParams()
const snackbar = useSnackbar()
const { session } = useSession()
const { addReactions } = useReactions()
const { reactionEntities, reactionsByShout, createReaction, deleteReaction, loadReactionsBy } =
const { reactionEntities, reactionsByShout, createReaction, deleteReaction, loadShoutRatings, loadCommentRatings } =
useReactions()
const [myRate, setMyRate] = createSignal<Reaction | undefined>()
const [ratingReactions, setRatingReactions] = createSignal<Reaction[]>([])
@ -70,12 +69,13 @@ export const RatingControl = (props: RatingControlProps) => {
// rating change
const handleRatingChange = async (isUpvote: boolean) => {
setIsLoading(true)
let error = ''
try {
if (isUpvoted()) {
await deleteRating(ReactionKind.Like)
} else if (isDownvoted()) {
await deleteRating(ReactionKind.Dislike)
} else {
if (isUpvoted() && isUpvote) return
if (isDownvoted() && !isUpvote) return
if (isUpvoted() && !isUpvote) error = (await deleteRating(ReactionKind.Like))?.error || ''
if (isDownvoted() && isUpvote) error = (await deleteRating(ReactionKind.Dislike))?.error || ''
if (!(isUpvoted() || isDownvoted())) {
props.comment?.shout.id &&
(await createReaction({
reaction: {
@ -85,13 +85,8 @@ export const RatingControl = (props: RatingControlProps) => {
}
}))
}
} catch {
snackbar?.showSnackbar({ type: 'error', body: t('Error') })
}
if (props.comment?.shout.slug) {
const rrr = await loadReactionsBy({ by: { shout: props.comment.shout.slug } })
addReactions(rrr)
} catch(err) {
snackbar?.showSnackbar({ type: 'error', body: `${t('Error')}: ${error || err || ''}` })
}
setIsLoading(false)
}
@ -138,16 +133,12 @@ export const RatingControl = (props: RatingControlProps) => {
: []
)
const loadMoreReactions = async () => {
if (!(props.shout?.id || props.comment?.id)) return [] as LoadMoreItems
setRatingLoading(true)
const next = ratingPage() + 1
const offset = VOTERS_PER_PAGE * next
const opts: QueryLoad_Reactions_ByArgs = {
by: { rating: true, shout: props.shout?.slug },
limit: VOTERS_PER_PAGE,
offset
}
const rrr = await loadReactionsBy(opts)
rrr && addReactions(rrr)
const offset = RATINGS_PER_PAGE * next
const loader = props.comment ? loadCommentRatings : loadShoutRatings
const rrr = await loader(props.shout?.id || 0, RATINGS_PER_PAGE, offset)
rrr && setRatingPage(next)
setRatingLoading(false)
return rrr as LoadMoreItems

View File

@ -61,7 +61,7 @@ export const AuthorView = (props: AuthorViewProps) => {
on(
[() => session()?.user?.app_data?.profile, () => props.authorSlug || ''],
async ([me, slug]) => {
console.debug('check if my profile')
console.debug('[AuthorView] checking if my profile')
const my = slug && me?.slug === slug
if (my) {
console.debug('[Author] my profile precached')
@ -86,7 +86,7 @@ export const AuthorView = (props: AuthorViewProps) => {
() => authorsEntities()[props.author?.slug || props.authorSlug || ''],
async (found) => {
if (!found) return
setAuthor(found)
console.debug('[AuthorView] ')
console.info(`[Author] profile for @${found.slug} fetched`)
const followsResp = await query(getAuthorFollowsQuery, { slug: found.slug }).toPromise()
const follows = followsResp?.data?.get_author_followers || {}
@ -96,6 +96,7 @@ export const AuthorView = (props: AuthorViewProps) => {
setFollowers(followersResp?.data?.get_author_followers || [])
console.info(`[Author] followers for @${found.slug} fetched`)
setIsFetching(false)
setTimeout(() => setAuthor(found), 1)
},
{ defer: true }
)
@ -123,7 +124,37 @@ export const AuthorView = (props: AuthorViewProps) => {
(tab) => tab && console.log('[views.Author] profile tab switched')
)
)
const AuthorFeed = () => (
<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={props.shouts?.length || 0}>
<Show when={props.shouts?.length === 1}>
<Row1 article={props.shouts?.[0] as Shout} noauthor={true} nodate={true} />
</Show>
<Show when={props.shouts?.length === 2}>
<Row2 articles={props.shouts as Shout[]} isEqual={true} noauthor={true} nodate={true} />
</Show>
<Show when={props.shouts?.length === 3}>
<Row3 articles={props.shouts as Shout[]} noauthor={true} nodate={true} />
</Show>
<Show when={props.shouts && props.shouts.length > 3}>
<For each={pages()}>
{(page) => (
<>
<Row1 article={page[0]} noauthor={true} nodate={true} />
<Row2 articles={page.slice(1, 3)} isEqual={true} noauthor={true} />
<Row1 article={page[3]} noauthor={true} nodate={true} />
<Row2 articles={page.slice(4, 6)} isEqual={true} noauthor={true} />
<Row1 article={page[6]} noauthor={true} nodate={true} />
<Row2 articles={page.slice(7, 9)} isEqual={true} noauthor={true} />
</>
)}
</For>
</Show>
</Show>
</Show>
)
return (
<div class={styles.authorPage}>
<div class="wide-container">
@ -229,34 +260,8 @@ export const AuthorView = (props: AuthorViewProps) => {
</div>
</Show>
<Show when={Array.isArray(props.shouts) && props.shouts.length > 0 && props.shouts[0]}>
<Row1 article={props.shouts?.[0] as Shout} noauthor={true} nodate={true} />
<AuthorFeed />
<Show when={props.shouts && props.shouts.length > 1}>
<Switch>
<Match when={props.shouts && props.shouts.length === 2}>
<Row2 articles={props.shouts as Shout[]} isEqual={true} noauthor={true} nodate={true} />
</Match>
<Match when={props.shouts && props.shouts.length === 3}>
<Row3 articles={props.shouts as Shout[]} noauthor={true} nodate={true} />
</Match>
<Match when={props.shouts && props.shouts.length > 3}>
<For each={pages()}>
{(page) => (
<>
<Row1 article={page[0]} noauthor={true} nodate={true} />
<Row2 articles={page.slice(1, 3)} isEqual={true} noauthor={true} />
<Row1 article={page[3]} noauthor={true} nodate={true} />
<Row2 articles={page.slice(4, 6)} isEqual={true} noauthor={true} />
<Row1 article={page[6]} noauthor={true} nodate={true} />
<Row2 articles={page.slice(7, 9)} isEqual={true} noauthor={true} />
</>
)}
</For>
</Match>
</Switch>
</Show>
</Show>
</Match>
</Switch>
</div>

View File

@ -5,6 +5,7 @@ import { Author, Reaction, Shout } from '~/graphql/schema/core.gen'
import { byCreated } from '~/lib/sort'
import { SortFunction } from '~/types/common'
import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll'
import { Loading } from './Loading'
export type LoadMoreItems = Shout[] | Author[] | Reaction[]
@ -52,11 +53,11 @@ export const LoadMoreWrapper = (props: LoadMoreProps) => {
return (
<>
{props.children}
<Show when={isLoadMoreButtonVisible() && !props.hidden}>
<Show when={isLoading()}><Loading /></Show>
<Show when={isLoadMoreButtonVisible() && !props.hidden && !isLoading()}>
<div class="load-more-container">
<Button
onClick={loadItems}
disabled={isLoading()}
value={t('Load more')}
title={`${items().length} ${t('loaded')}`}
/>

View File

@ -2,7 +2,7 @@ import type { JSX } from 'solid-js'
import { createContext, onCleanup, useContext } from 'solid-js'
import { createStore, reconcile } from 'solid-js/store'
import { loadReactions } from '~/graphql/api/public'
import { loadCommentRatings, loadReactions, loadShoutComments, loadShoutRatings } from '~/graphql/api/public'
import createReactionMutation from '~/graphql/mutation/core/reaction-create'
import destroyReactionMutation from '~/graphql/mutation/core/reaction-destroy'
import updateReactionMutation from '~/graphql/mutation/core/reaction-update'
@ -17,10 +17,16 @@ import { useGraphQL } from './graphql'
import { useLocalize } from './localize'
import { useSnackbar } from './ui'
export const COMMENTS_PER_PAGE = 50
export const RATINGS_PER_PAGE = 100
type ReactionsContextType = {
reactionEntities: Record<number, Reaction>
reactionsByShout: Record<string, Reaction[]>
loadReactionsBy: (args: QueryLoad_Reactions_ByArgs) => Promise<Reaction[]>
loadShoutComments: (shout: number, limit?: number, offset?: number) => Promise<Reaction[]>
loadShoutRatings: (shout: number, limit?: number, offset?: number) => Promise<Reaction[]>
loadCommentRatings: (comment: number, limit?: number, offset?: number) => Promise<Reaction[]>
createReaction: (reaction: MutationCreate_ReactionArgs) => Promise<void>
updateReaction: (reaction: MutationUpdate_ReactionArgs) => Promise<Reaction>
deleteReaction: (id: number) => Promise<{ error: string } | null>
@ -63,6 +69,30 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
return result
}
const loadShoutRatingsAdding = async (shout: number, limit = RATINGS_PER_PAGE, offset = 0): Promise<Reaction[]> => {
const fetcher = await loadShoutRatings({ shout, limit, offset })
const result = (await fetcher()) || []
console.debug('[context.reactions] shout ratings loaded', result)
result && addReactions(result)
return result
}
const loadCommentRatingsAdding = async (comment: number, limit = RATINGS_PER_PAGE, offset = 0): Promise<Reaction[]> => {
const fetcher = await loadCommentRatings({ comment, limit, offset })
const result = (await fetcher()) || []
console.debug('[context.reactions] shout ratings loaded', result)
result && addReactions(result)
return result
}
const loadShoutCommentsAdding = async (shout: number, limit = COMMENTS_PER_PAGE, offset = 0): Promise<Reaction[]> => {
const fetcher = await loadShoutComments({ shout, limit, offset })
const result = (await fetcher()) || []
console.debug('[context.reactions] shout comments loaded', result)
result && addReactions(result)
return result
}
const createReaction = async (input: MutationCreate_ReactionArgs): Promise<void> => {
const resp = await mutation(createReactionMutation, input).toPromise()
const { error, reaction } = resp?.data?.create_reaction || {}
@ -122,6 +152,9 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
const actions = {
loadReactionsBy,
loadShoutComments: loadShoutCommentsAdding,
loadShoutRatings: loadShoutRatingsAdding,
loadCommentRatings: loadCommentRatingsAdding,
createReaction,
updateReaction,
deleteReaction,

View File

@ -1,11 +1,14 @@
import { cache } from '@solidjs/router'
import { defaultClient } from '~/context/graphql'
import loadShoutCommentsQuery from '~/graphql/query/core/article-comments-load'
import getShoutQuery from '~/graphql/query/core/article-load'
import loadShoutRatingsQuery from '~/graphql/query/core/article-ratings-load'
import loadShoutsByQuery from '~/graphql/query/core/articles-load-by'
import loadShoutsSearchQuery from '~/graphql/query/core/articles-load-search'
import getAuthorQuery from '~/graphql/query/core/author-by'
import loadAuthorsAllQuery from '~/graphql/query/core/authors-all'
import loadAuthorsByQuery from '~/graphql/query/core/authors-load-by'
import loadCommentRatingsQuery from '~/graphql/query/core/comment-ratings-load'
import loadReactionsByQuery from '~/graphql/query/core/reactions-load-by'
import loadFollowersByTopicQuery from '~/graphql/query/core/topic-followers'
import loadTopicsQuery from '~/graphql/query/core/topics-all'
@ -16,6 +19,7 @@ import {
QueryGet_ShoutArgs,
QueryLoad_Authors_ByArgs,
QueryLoad_Reactions_ByArgs,
QueryLoad_Shout_RatingsArgs,
QueryLoad_Shouts_SearchArgs,
Reaction,
Shout,
@ -57,16 +61,39 @@ export const loadShouts = (options: LoadShoutsOptions) => {
}, `shouts-${filter}-${page}`)
}
export const loadShoutComments = (options: QueryLoad_Shout_RatingsArgs) => {
const page = `${options.offset || 0}-${(options.limit||1) + (options.offset || 0)}`
return cache(async () => {
const resp = await defaultClient.query(loadShoutCommentsQuery, options).toPromise()
const result = resp?.data?.load_reactions_by
if (result) return result as Reaction[]
}, `shout-${options.shout}-comments-${page}`)
}
export const loadShoutRatings = (options: QueryLoad_Shout_RatingsArgs) => {
const page = `${options.offset || 0}-${(options.limit||1) + (options.offset || 0)}`
return cache(async () => {
const resp = await defaultClient.query(loadShoutRatingsQuery, options).toPromise()
const result = resp?.data?.load_reactions_by
if (result) return result as Reaction[]
}, `shout-${options.shout}-ratings-${page}`)
}
// biome-ignore lint/suspicious/noExplicitAny: FIXME: wait backend
export const loadCommentRatings = (options: any) => {
const page = `${options.offset || 0}-${(options.limit||1) + (options.offset || 0)}`
return cache(async () => {
const resp = await defaultClient.query(loadCommentRatingsQuery, options).toPromise()
const result = resp?.data?.load_reactions_by
if (result) return result as Reaction[]
}, `comment-${options.comment}-ratings-${page}`)
}
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)}`
const filter = new URLSearchParams(options.by as Record<string, string>)
// console.debug(options)
return cache(async () => {
const resp = await defaultClient.query(loadReactionsByQuery, options).toPromise()
const result = resp?.data?.load_reactions_by
@ -75,7 +102,6 @@ export const loadReactions = (options: QueryLoad_Reactions_ByArgs) => {
}
export const getShout = (options: QueryGet_ShoutArgs) => {
// console.debug('[lib.api] get shout options', options)
return cache(
async () => {
const resp = await defaultClient.query(getShoutQuery, { ...options }).toPromise()

View File

@ -0,0 +1,29 @@
import { gql } from '@urql/core'
export default gql`
query LoadReactions($shout: Int!, $limit: Int, $offset: Int) {
load_shout_comments(shout: $shout, limit: $limit, offset: $offset) {
id
kind
body
reply_to
shout {
id
slug
title
}
created_by {
id
name
slug
pic
created_at
}
created_at
updated_at
stat {
rating
}
}
}
`

View File

@ -0,0 +1,29 @@
import { gql } from '@urql/core'
export default gql`
query LoadReactions($shout: Int!, $limit: Int, $offset: Int) {
load_shout_ratings(shout: $shout, limit: $limit, offset: $offset) {
id
kind
body
reply_to
shout {
id
slug
title
}
created_by {
id
name
slug
pic
created_at
}
created_at
updated_at
stat {
rating
}
}
}
`

View File

@ -0,0 +1,29 @@
import { gql } from '@urql/core'
export default gql`
query LoadReactions($comment: Int!, $limit: Int, $offset: Int) {
load_comment_ratings(comment: $comment, limit: $limit, offset: $offset) {
id
kind
body
reply_to
shout {
id
slug
title
}
created_by {
id
name
slug
pic
created_at
}
created_at
updated_at
stat {
rating
}
}
}
`

View File

@ -1,5 +1,5 @@
import { RouteSectionProps } from '@solidjs/router'
import { ErrorBoundary, createEffect, createMemo, createSignal, on } from 'solid-js'
import { RouteSectionProps, createAsync } from '@solidjs/router'
import { ErrorBoundary, createEffect, createMemo } from 'solid-js'
import { AuthorView } from '~/components/Views/Author'
import { FourOuFourView } from '~/components/Views/FourOuFour'
import { LoadMoreItems, LoadMoreWrapper } from '~/components/_shared/LoadMoreWrapper'
@ -33,6 +33,7 @@ const fetchAllTopics = async () => {
const fetchAuthor = async (slug: string) => {
const authorFetcher = loadAuthors({ by: { slug }, limit: 1, offset: 0 } as QueryLoad_Authors_ByArgs)
const aaa = await authorFetcher()
console.debug(aaa)
return aaa?.[0]
}
@ -50,11 +51,17 @@ export const route = {
export type AuthorPageProps = { articles?: Shout[]; author?: Author; topics?: Topic[] }
export default function AuthorPage(props: RouteSectionProps<AuthorPageProps>) {
const { addAuthor, authorsEntities } = useAuthors()
const [author, setAuthor] = createSignal<Author | undefined>(undefined)
const { authorsEntities } = useAuthors()
const { addFeed, feedByAuthor } = useFeed()
const { t } = useLocalize()
const author = createAsync(async() => props.data.author || authorsEntities()[props.params.slug] || await fetchAuthor(props.params.slug))
const shoutsByAuthor = createMemo(() => feedByAuthor()[props.params.slug])
const title = createMemo(() => `${author()?.name || ''}`)
const cover = createMemo(() =>
author()?.pic
? getImageUrl(author()?.pic || '', { width: 1200 })
: getImageUrl('production/image/logo_image.png')
)
createEffect(() => {
if (author()) {
@ -67,32 +74,8 @@ export default function AuthorPage(props: RouteSectionProps<AuthorPageProps>) {
}
})
const cover = createMemo(() =>
author()?.pic
? getImageUrl(author()?.pic || '', { width: 1200 })
: getImageUrl('production/image/logo_image.png')
)
// author shouts
const { addFeed, feedByAuthor } = useFeed()
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 loadAuthorShoutsMore = async (offset: number) => {
const loadedShouts = await fetchAuthorShouts(props.params.slug, offset)
loadedShouts && addFeed(loadedShouts)

View File

@ -125,7 +125,7 @@ export default (props: RouteSectionProps<{ shouts: Shout[]; topics: Topic[] }>)
key="feed"
desc="Independent media project about culture, science, art and society with horizontal editing"
>
<LoadMoreWrapper loadFunction={loadMoreFeed} pageSize={AUTHORS_PER_PAGE}>
<LoadMoreWrapper loadFunction={loadMoreFeed} pageSize={AUTHORS_PER_PAGE} hidden={!feed()}>
<ReactionsProvider>
<Feed shouts={feed() || (shouts() as Shout[])} order={order() as FeedProps['order']} />
</ReactionsProvider>