From 2d89f62864b612f7a713b9fbb94d183905783214 Mon Sep 17 00:00:00 2001 From: Untone Date: Sat, 6 Jul 2024 03:59:01 +0300 Subject: [PATCH] showup --- src/components/Nav/Header/Header.tsx | 16 +---- src/components/Nav/Header/HeaderLink.tsx | 2 +- .../Views/AllAuthors/AllAuthors.tsx | 12 ++-- .../ProfileSubscriptions.tsx | 6 +- src/context/authors.tsx | 50 ++++++++-------- src/context/feed.tsx | 59 +++++++----------- src/graphql/api/public.ts | 24 +++++++- src/graphql/query/core/authors-load-by.ts | 2 +- src/routes/author/(all-authors).tsx | 60 +++++++------------ 9 files changed, 103 insertions(+), 128 deletions(-) diff --git a/src/components/Nav/Header/Header.tsx b/src/components/Nav/Header/Header.tsx index 2c26a015..860cf41a 100644 --- a/src/components/Nav/Header/Header.tsx +++ b/src/components/Nav/Header/Header.tsx @@ -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 (
{
{props.title}
-
    +
      !fixed() && toggleFixed()}> toggleSubnavigation(true, setIsZineVisible)} onMouseOut={hideSubnavigation} href="/" active={isZineVisible()} body={t('Journal')} - onClick={(event: MouseEvent) => handleToggleMenuByLink(event, '')} /> 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')} /> 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')} /> hideSubnavigation(event, 0)} onMouseOut={(event?: MouseEvent) => hideSubnavigation(event, 0)} href="/author" body={t('Authors')} - onClick={(event: MouseEvent) => handleToggleMenuByLink(event, 'author')} /> 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')} />
    diff --git a/src/components/Nav/Header/HeaderLink.tsx b/src/components/Nav/Header/HeaderLink.tsx index 483d6af7..992b37c9 100644 --- a/src/components/Nav/Header/HeaderLink.tsx +++ b/src/components/Nav/Header/HeaderLink.tsx @@ -20,7 +20,7 @@ export const Link = (props: Props) => { classList={{ 'view-switcher__item--selected': props.href === `/${loc.pathname}` }} > {children}} > { 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) => { diff --git a/src/components/Views/ProfileSubscriptions/ProfileSubscriptions.tsx b/src/components/Views/ProfileSubscriptions/ProfileSubscriptions.tsx index 671b7a65..06dc9461 100644 --- a/src/components/Views/ProfileSubscriptions/ProfileSubscriptions.tsx +++ b/src/components/Views/ProfileSubscriptions/ProfileSubscriptions.tsx @@ -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() diff --git a/src/context/authors.tsx b/src/context/authors.tsx index aee02188..4ba703cf 100644 --- a/src/context/authors.tsx +++ b/src/context/authors.tsx @@ -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 addAuthors: (authors: Author[]) => void addAuthor: (author: Author) => void - loadAuthor: (slug: string) => Promise + loadAuthor: (args: QueryGet_AuthorArgs) => Promise loadAuthors: (args: QueryLoad_Authors_ByArgs) => Promise topAuthors: Accessor authorsByTopic: Accessor<{ [topicSlug: string]: Author[] }> @@ -52,7 +56,6 @@ export const AuthorsProvider = (props: { children: JSX.Element }) => { const [authorsSorted, setAuthorsSorted] = createSignal([]) const [sortBy, setSortBy] = createSignal>() const { feedByAuthor } = useFeed() - const { query } = useGraphQL() const setAuthorsSort = (stat: string) => setSortBy((_) => byStat(stat) as SortFunction) // Эффект для отслеживания изменений сигнала 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 => { + const loadAuthor = async (opts: QueryGet_AuthorArgs): Promise => { 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 => { + const loadAuthorsPage = async (args: QueryLoad_Authors_ByArgs): Promise => { 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 => { - 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 diff --git a/src/context/feed.tsx b/src/context/feed.tsx index 829917a0..e412de80 100644 --- a/src/context/feed.tsx +++ b/src/context/feed.tsx @@ -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([]) const [topMonthFeed, setTopMonthFeed] = createSignal([]) 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 => { - 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) } diff --git a/src/graphql/api/public.ts b/src/graphql/api/public.ts index 7efe2cff..8fdef81e 100644 --- a/src/graphql/api/public.ts +++ b/src/graphql/api/public.ts @@ -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) 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) @@ -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 () => { diff --git a/src/graphql/query/core/authors-load-by.ts b/src/graphql/query/core/authors-load-by.ts index 87d29db2..2d60ddeb 100644 --- a/src/graphql/query/core/authors-load-by.ts +++ b/src/graphql/query/core/authors-load-by.ts @@ -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 diff --git a/src/routes/author/(all-authors).tsx b/src/routes/author/(all-authors).tsx index f0c94a65..4cdb85e9 100644 --- a/src/routes/author/(all-authors).tsx +++ b/src/routes/author/(all-authors).tsx @@ -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 topicsFetcher = loadAuthors(opts) - return await topicsFetcher() +const fetchAuthorsWithStat = async (offset = 0, order?: string) => { + const by: AuthorsBy = { order } + const authorsFetcher = loadAuthors({ by, offset, limit: AUTHORS_PER_PAGE }) + return await authorsFetcher() } -const AUTHORS_PER_PAGE = 20 + +const fetchAllAuthors = async () => { + const authorsAllFetcher = loadAuthorsAll() + return await authorsAllFetcher() +} + 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(async () => props.data.authors || (await fetchData())) - createEffect(() => addAuthors(authors() || [])) + const { authorsSorted, addAuthors } = useAuthors() + const authors = createAsync( + async () => authorsSorted?.() || props.data.authors || (await fetchAllAuthors()) + ) + createReaction(() => typeof addAuthors === 'function' && addAuthors?.(authors() || [])) return (