This commit is contained in:
Untone 2024-07-06 03:59:01 +03:00
parent 6104079f09
commit 2d89f62864
9 changed files with 103 additions and 128 deletions

View File

@ -1,4 +1,4 @@
import { A, redirect, useLocation, useNavigate, useSearchParams } from '@solidjs/router'
import { A, redirect, useSearchParams } from '@solidjs/router'
import { clsx } from 'clsx'
import { For, Show, createEffect, createSignal, onCleanup, onMount } from 'solid-js'
import { useLocalize } from '~/context/localize'
@ -54,8 +54,6 @@ export const Header = (props: Props) => {
const [isZineVisible, setIsZineVisible] = createSignal(false)
const [isFeedVisible, setIsFeedVisible] = createSignal(false)
const { session } = useSession()
const loc = useLocation()
const navigate = useNavigate()
const toggleFixed = () => setFixed(!fixed())
const tag = (topic: Topic) =>
@ -148,11 +146,6 @@ export const Header = (props: Props) => {
}, time)
}
const handleToggleMenuByLink = (event: MouseEvent, route: string) => {
event.preventDefault()
if (fixed()) toggleFixed()
if (loc.pathname !== route) navigate(route)
}
return (
<header
class={styles.mainHeader}
@ -198,14 +191,13 @@ export const Header = (props: Props) => {
<div class={styles.articleHeader}>{props.title}</div>
</Show>
<div class={clsx(styles.mainNavigation, { [styles.fixed]: fixed() })}>
<ul class="view-switcher">
<ul class="view-switcher" onClick={() => !fixed() && toggleFixed()}>
<Link
onMouseOver={() => toggleSubnavigation(true, setIsZineVisible)}
onMouseOut={hideSubnavigation}
href="/"
active={isZineVisible()}
body={t('Journal')}
onClick={(event: MouseEvent) => handleToggleMenuByLink(event, '')}
/>
<Link
onMouseOver={() => toggleSubnavigation(true, setIsFeedVisible)}
@ -213,7 +205,6 @@ export const Header = (props: Props) => {
href="/feed"
active={isFeedVisible()}
body={t('Feed')}
onClick={(event: MouseEvent) => handleToggleMenuByLink(event, 'feed')}
/>
<Link
onMouseOver={() => toggleSubnavigation(true, setIsTopicsVisible)}
@ -221,14 +212,12 @@ export const Header = (props: Props) => {
href="/topic"
active={isTopicsVisible()}
body={t('Topics')}
onClick={(event: MouseEvent) => handleToggleMenuByLink(event, 'topic')}
/>
<Link
onMouseOver={(event?: MouseEvent) => hideSubnavigation(event, 0)}
onMouseOut={(event?: MouseEvent) => hideSubnavigation(event, 0)}
href="/author"
body={t('Authors')}
onClick={(event: MouseEvent) => handleToggleMenuByLink(event, 'author')}
/>
<Link
onMouseOver={() => toggleSubnavigation(true, setIsKnowledgeBaseVisible)}
@ -236,7 +225,6 @@ export const Header = (props: Props) => {
href="/guide"
body={t('Knowledge base')}
active={isKnowledgeBaseVisible()}
onClick={(event: MouseEvent) => handleToggleMenuByLink(event, 'guide')}
/>
</ul>

View File

@ -20,7 +20,7 @@ export const Link = (props: Props) => {
classList={{ 'view-switcher__item--selected': props.href === `/${loc.pathname}` }}
>
<ConditionalWrapper
condition={props.href === `/${loc.pathname}`}
condition={props.href !== `/${loc.pathname}`}
wrapper={(children) => <A href={props.href || '/'}>{children}</A>}
>
<span

View File

@ -1,7 +1,7 @@
import { Meta } from '@solidjs/meta'
import { useSearchParams } from '@solidjs/router'
import { clsx } from 'clsx'
import { For, Show, createEffect, createMemo, createSignal, on } from 'solid-js'
import { For, Show, createEffect, createMemo, createSignal, on, onMount } from 'solid-js'
import { Loading } from '~/components/_shared/Loading'
import { SearchField } from '~/components/_shared/SearchField'
import { useAuthors } from '~/context/authors'
@ -31,10 +31,12 @@ export const AllAuthors = (props: Props) => {
const { t, lang } = useLocalize()
const [searchQuery, setSearchQuery] = createSignal('')
const alphabet = createMemo(() => ABC[lang()] || ABC['ru'])
const [searchParams] = useSearchParams<{ by?: string }>()
const [searchParams, changeSearchParams] = useSearchParams<{ by?: string }>()
const { authorsSorted, addAuthors, setAuthorsSort } = useAuthors()
createEffect(on(() => searchParams?.by || 'name', setAuthorsSort, {}))
createEffect(on(() => props.authors || [], addAuthors, {}))
onMount(() => !searchParams?.by && changeSearchParams({ by: 'name' }))
createEffect(on(() => searchParams?.by || 'name', setAuthorsSort || ((_) => null), {}))
createEffect(on(() => props.authors || [], addAuthors || ((_) => null), {}))
const filteredAuthors = createMemo(() => {
const query = searchQuery().toLowerCase()
@ -165,7 +167,7 @@ export const AllAuthors = (props: Props) => {
</Show>
<Show when={searchParams?.by !== 'name' && props.isLoaded}>
<AuthorsList
allAuthorsLength={authorsSorted()?.length || 0}
allAuthorsLength={authorsSorted?.()?.length || 0}
searchQuery={searchQuery()}
query={searchParams?.by === 'followers' ? 'followers' : 'shouts'}
/>

View File

@ -1,19 +1,17 @@
import { clsx } from 'clsx'
import { For, Show, createEffect, createSignal, on } from 'solid-js'
import { Loading } from '~/components/_shared/Loading'
import { SearchField } from '~/components/_shared/SearchField'
import { FollowsFilter, useFollowing } from '~/context/following'
import { useLocalize } from '~/context/localize'
import { Author, Topic } from '~/graphql/schema/core.gen'
import { dummyFilter } from '~/lib/dummyFilter'
import stylesSettings from '../../../styles/FeedSettings.module.scss'
import { AuthorBadge } from '../../Author/AuthorBadge'
import { ProfileSettingsNavigation } from '../../Nav/ProfileSettingsNavigation'
import styles from '../../ProfileSettings/Seetings.module.scss'
import { TopicBadge } from '../../Topic/TopicBadge'
import styles from '../../../pages/profile/Settings.module.scss'
import stylesSettings from '../../../styles/FeedSettings.module.scss'
export const ProfileSubscriptions = () => {
const { t, lang } = useLocalize()
const { follows } = useFollowing()

View File

@ -8,13 +8,17 @@ import {
on,
useContext
} from 'solid-js'
import loadAuthorByQuery from '~/graphql/query/core/author-by'
import loadAuthorsAllQuery from '~/graphql/query/core/authors-all'
import loadAuthorsByQuery from '~/graphql/query/core/authors-load-by'
import { Author, Maybe, QueryLoad_Authors_ByArgs, Shout, Topic } from '~/graphql/schema/core.gen'
import { getAuthor, loadAuthors, loadAuthorsAll } from '~/graphql/api/public'
import {
Author,
Maybe,
QueryGet_AuthorArgs,
QueryLoad_Authors_ByArgs,
Shout,
Topic
} from '~/graphql/schema/core.gen'
import { byStat } from '~/lib/sortby'
import { useFeed } from './feed'
import { useGraphQL } from './graphql'
const TOP_AUTHORS_COUNT = 5
@ -35,7 +39,7 @@ type AuthorsContextType = {
authorsSorted: Accessor<Author[]>
addAuthors: (authors: Author[]) => void
addAuthor: (author: Author) => void
loadAuthor: (slug: string) => Promise<void>
loadAuthor: (args: QueryGet_AuthorArgs) => Promise<void>
loadAuthors: (args: QueryLoad_Authors_ByArgs) => Promise<void>
topAuthors: Accessor<Author[]>
authorsByTopic: Accessor<{ [topicSlug: string]: Author[] }>
@ -52,7 +56,6 @@ export const AuthorsProvider = (props: { children: JSX.Element }) => {
const [authorsSorted, setAuthorsSorted] = createSignal<Author[]>([])
const [sortBy, setSortBy] = createSignal<SortFunction<Author>>()
const { feedByAuthor } = useFeed()
const { query } = useGraphQL()
const setAuthorsSort = (stat: string) => setSortBy((_) => byStat(stat) as SortFunction<Author>)
// Эффект для отслеживания изменений сигнала sortBy и обновления authorsSorted
@ -69,6 +72,7 @@ export const AuthorsProvider = (props: { children: JSX.Element }) => {
)
const addAuthors = (newAuthors: Author[]) => {
console.debug('[context.authors] storing new authors:', newAuthors)
setAuthors((prevAuthors) => {
const updatedAuthors = { ...prevAuthors }
newAuthors.forEach((author) => {
@ -86,13 +90,11 @@ export const AuthorsProvider = (props: { children: JSX.Element }) => {
})
}
const loadAuthor = async (slug: string): Promise<void> => {
const loadAuthor = async (opts: QueryGet_AuthorArgs): Promise<void> => {
try {
const resp = await query(loadAuthorByQuery, { slug }).toPromise()
if (resp) {
const author = resp.data.get_author
if (author?.id) addAuthor(author)
}
const fetcher = await getAuthor(opts)
const author = await fetcher()
if (author) addAuthor(author as Author)
} catch (error) {
console.error('Error loading author:', error)
throw error
@ -123,15 +125,13 @@ export const AuthorsProvider = (props: { children: JSX.Element }) => {
return sortedTopAuthors
})
const loadAuthors = async (args: QueryLoad_Authors_ByArgs): Promise<void> => {
const loadAuthorsPage = async (args: QueryLoad_Authors_ByArgs): Promise<void> => {
try {
const resp = await query(loadAuthorsByQuery, { ...args }).toPromise()
if (resp) {
const author = resp.data.get_author
if (author?.id) addAuthor(author)
}
const fetcher = await loadAuthors(args)
const data = await fetcher()
if (data) addAuthors(data as Author[])
} catch (error) {
console.error('Error loading author:', error)
console.error('Error loading authors:', error)
throw error
}
}
@ -165,9 +165,11 @@ export const AuthorsProvider = (props: { children: JSX.Element }) => {
return result
})
const loadAllAuthors = async (): Promise<Author[]> => {
const resp = await query(loadAuthorsAllQuery, {}).toPromise()
return resp?.data?.get_authors_all || []
const loadAllAuthors = async () => {
const fetcher = loadAuthorsAll()
const data = await fetcher()
addAuthors(data || [])
return data || []
}
const contextValue: AuthorsContextType = {
@ -177,7 +179,7 @@ export const AuthorsProvider = (props: { children: JSX.Element }) => {
addAuthors,
addAuthor,
loadAuthor,
loadAuthors,
loadAuthors: loadAuthorsPage,
topAuthors,
authorsByTopic,
setAuthorsSort

View File

@ -1,10 +1,8 @@
import { createLazyMemo } from '@solid-primitives/memo'
import { makePersisted } from '@solid-primitives/storage'
import { Accessor, JSX, createContext, createSignal, useContext } from 'solid-js'
import getShoutBySlug from '~/graphql/query/core/article-load'
import loadShoutsByQuery from '~/graphql/query/core/articles-load-by'
import loadShoutsFeed from '~/graphql/query/core/articles-load-feed'
import loadShoutsSearchQuery from '~/graphql/query/core/articles-load-search'
import { loadFollowedShouts } from '~/graphql/api/private'
import { loadShoutsSearch as fetchShoutsSearch, getShout, loadShouts } from '~/graphql/api/public'
import {
Author,
LoadShoutsOptions,
@ -51,7 +49,6 @@ export const FeedProvider = (props: { children: JSX.Element }) => {
const [topFeed, setTopFeed] = createSignal<Shout[]>([])
const [topMonthFeed, setTopMonthFeed] = createSignal<Shout[]>([])
const [feedByLayout, _setFeedByLayout] = createSignal<{ [layout: string]: Shout[] }>({})
const { query } = useGraphQL()
const [seen, setSeen] = makePersisted(createSignal<{ [slug: string]: number }>({}), {
name: 'discoursio-seen'
})
@ -133,9 +130,8 @@ export const FeedProvider = (props: { children: JSX.Element }) => {
// Load a single shout by slug and update the articleEntities and sortedFeed state
const loadShout = async (slug: string): Promise<void> => {
const resp = await query(getShoutBySlug, { slug }).toPromise()
if (!resp) return
const newArticle = resp?.data?.get_shout as Shout
const fetcher = await getShout({ slug })
const newArticle = (await fetcher()) as Shout
addFeed([newArticle])
const newArticleIndex = sortedFeed().findIndex((s) => s.id === newArticle.id)
if (newArticleIndex >= 0) {
@ -149,36 +145,26 @@ export const FeedProvider = (props: { children: JSX.Element }) => {
const loadShoutsBy = async (
options: LoadShoutsOptions
): Promise<{ hasMore: boolean; newShouts: Shout[] }> => {
const resp = await query(loadShoutsByQuery, { options }).toPromise()
const result = resp?.data?.load_shouts_by || []
const fetcher = await loadShouts(options)
const result = (await fetcher()) || []
const hasMore = result.length !== options.limit + 1 && result.length !== 0
if (hasMore) {
result.splice(-1)
}
if (hasMore) result.splice(-1)
addFeed(result)
return { hasMore, newShouts: result }
}
const client = useGraphQL()
// Load the user's feed based on the provided options and update the articleEntities and sortedFeed state
const loadMyFeed = async (
options: LoadShoutsOptions
): Promise<{ hasMore: boolean; newShouts: Shout[] }> => {
if (!options.limit) options.limit = 0
options.limit += 1
const resp = await query(loadShoutsFeed, options).toPromise()
const result = resp?.data?.load_shouts_feed
const fetcher = await loadFollowedShouts(client, options)
const result = (await fetcher()) || []
const hasMore = result.length === options.limit + 1
if (hasMore) {
resp.data.splice(-1)
}
addFeed(resp.data || [])
return { hasMore, newShouts: resp.data.load_shouts_by }
if (hasMore) result.splice(-1)
addFeed(result || [])
return { hasMore, newShouts: result }
}
// Load shouts based on the search query and update the articleEntities and sortedFeed state
@ -186,16 +172,11 @@ export const FeedProvider = (props: { children: JSX.Element }) => {
options: QueryLoad_Shouts_SearchArgs
): Promise<{ hasMore: boolean; newShouts: Shout[] }> => {
options.limit = options?.limit || 0 + 1
const resp = await query(loadShoutsSearchQuery, options).toPromise()
const result = resp?.data?.load_shouts_search || []
const fetcher = await fetchShoutsSearch(options)
const result = (await fetcher()) || []
const hasMore = result.length === (options?.limit || 0) + 1
if (hasMore) {
result.splice(-1)
}
if (hasMore) result.splice(-1)
addFeed(result)
return { hasMore, newShouts: result }
}
@ -216,8 +197,8 @@ export const FeedProvider = (props: { children: JSX.Element }) => {
order_by: 'likes_stat',
limit: 10
}
const resp = await query(loadShoutsByQuery, options).toPromise()
const result = resp?.data?.load_shouts_by || []
const fetcher = await loadShouts(options)
const result = (await fetcher()) || []
addFeed(result)
setTopMonthFeed(result)
}
@ -228,8 +209,8 @@ export const FeedProvider = (props: { children: JSX.Element }) => {
order_by: 'likes_stat',
limit: 10
}
const resp = await query(loadShoutsByQuery, options).toPromise()
const result = resp?.data?.load_shouts_by || []
const fetcher = await loadShouts(options)
const result = (await fetcher()) || []
addFeed(result)
setTopFeed(result)
}

View File

@ -2,12 +2,15 @@ import { cache } from '@solidjs/router'
import { defaultClient } from '~/context/graphql'
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 loadReactionsByQuery from '~/graphql/query/core/reactions-load-by'
import loadTopicsQuery from '~/graphql/query/core/topics-all'
import {
Author,
LoadShoutsOptions,
QueryGet_AuthorArgs,
QueryGet_ShoutArgs,
QueryLoad_Authors_ByArgs,
QueryLoad_Reactions_ByArgs,
@ -29,11 +32,19 @@ export const loadAuthors = (options: QueryLoad_Authors_ByArgs) => {
const filter = new URLSearchParams(options.by as Record<string, string>)
return cache(async () => {
const resp = await defaultClient.query(loadAuthorsByQuery, { ...options }).toPromise()
const result = resp?.data?.load_shouts_by
const result = resp?.data?.load_authors_by
if (result) return result as Author[]
}, `authors-${filter}-${page}`)
}
export const loadAuthorsAll = () => {
return cache(async () => {
const resp = await defaultClient.query(loadAuthorsAllQuery, {}).toPromise()
const result = resp?.data?.get_authors_all
if (result) return result as Author[]
}, 'authors-all')
}
export const loadShouts = (options: LoadShoutsOptions) => {
const page = `${options.offset || 0}-${options.limit + (options.offset || 0)}`
const filter = new URLSearchParams(options.filters as Record<string, string>)
@ -65,6 +76,17 @@ export const getShout = (options: QueryGet_ShoutArgs) => {
)
}
export const getAuthor = (options: QueryGet_AuthorArgs) => {
return cache(
async () => {
const resp = await defaultClient.query(getAuthorQuery, { ...options }).toPromise()
const result = resp?.data?.get_author
if (result) return result as Author
},
`author-${options?.slug || options?.author_id}`
)
}
export const loadShoutsSearch = (options: QueryLoad_Shouts_SearchArgs) => {
const page = `${options.offset || 0}-${(options?.limit || 0) + (options.offset || 0)}`
return cache(async () => {

View File

@ -2,7 +2,7 @@ import { gql } from '@urql/core'
export default gql`
query AuthorsAllQuery($by: AuthorsBy!, $limit: Int, $offset: Int) {
get_authors_nostat(by: $by, limit: $limit, offset: $offset) {
get_authors(by: $by, limit: $limit, offset: $offset) {
id
slug
name

View File

@ -1,56 +1,38 @@
import { RouteDefinition, RouteLoadFuncArgs, type RouteSectionProps, createAsync } from '@solidjs/router'
import { Suspense, createEffect } from 'solid-js'
import { Suspense, createReaction } from 'solid-js'
import { AllAuthors } from '~/components/Views/AllAuthors'
import { AUTHORS_PER_PAGE } from '~/components/Views/AllAuthors/AllAuthors'
import { Loading } from '~/components/_shared/Loading'
import { PageLayout } from '~/components/_shared/PageLayout'
import { useAuthors } from '~/context/authors'
import { useLocalize } from '~/context/localize'
import { ReactionsProvider } from '~/context/reactions'
import { loadAuthors } from '~/graphql/api/public'
import { Author, QueryLoad_Authors_ByArgs } from '~/graphql/schema/core.gen'
import { loadAuthors, loadAuthorsAll } from '~/graphql/api/public'
import { Author, AuthorsBy } from '~/graphql/schema/core.gen'
const fetchData = async () => {
const opts: QueryLoad_Authors_ByArgs = {
by: {
after: undefined,
created_at: undefined,
last_seen: undefined,
name: undefined,
order: undefined,
slug: undefined,
stat: undefined,
topic: undefined
const fetchAuthorsWithStat = async (offset = 0, order?: string) => {
const by: AuthorsBy = { order }
const authorsFetcher = loadAuthors({ by, offset, limit: AUTHORS_PER_PAGE })
return await authorsFetcher()
}
const fetchAllAuthors = async () => {
const authorsAllFetcher = loadAuthorsAll()
return await authorsAllFetcher()
}
const topicsFetcher = loadAuthors(opts)
return await topicsFetcher()
}
const AUTHORS_PER_PAGE = 20
export const route = {
load: (_args: RouteLoadFuncArgs) => {
const opts: QueryLoad_Authors_ByArgs = {
by: {
after: undefined,
created_at: undefined,
last_seen: undefined,
name: undefined,
order: undefined,
slug: undefined,
stat: undefined,
topic: undefined
},
limit: AUTHORS_PER_PAGE,
offset: 0
}
return loadAuthors(opts)
}
load: ({ location: { query } }: RouteLoadFuncArgs) =>
fetchAuthorsWithStat(Number.parseInt(query.offset), query.by || 'name')
} satisfies RouteDefinition
export default function AllAuthorsPage(props: RouteSectionProps<{ authors: Author[] }>) {
const { t } = useLocalize()
const { addAuthors } = useAuthors()
const authors = createAsync<Author[]>(async () => props.data.authors || (await fetchData()))
createEffect(() => addAuthors(authors() || []))
const { authorsSorted, addAuthors } = useAuthors()
const authors = createAsync<Author[]>(
async () => authorsSorted?.() || props.data.authors || (await fetchAllAuthors())
)
createReaction(() => typeof addAuthors === 'function' && addAuthors?.(authors() || []))
return (
<PageLayout withPadding={true} title={`${t('Discours')} :: ${t('All authors')}`}>
<ReactionsProvider>