diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100755 index d4a43dd1..00000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - -npm run pre-commit diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 113182a4..5dfd02bb 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -291,6 +291,7 @@ "Profile": "Profile", "Publications": "Publications", "PublicationsWithCount": "{count, plural, =0 {no publications} one {{count} publication} other {{count} publications}}", + "FollowersWithCount": "{count, plural, =0 {no followers} one {{count} follower} other {{count} followers}}", "Publish Album": "Publish Album", "Publish Settings": "Publish Settings", "Published": "Published", diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index bcdc7e2c..647917b0 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -309,9 +309,10 @@ "Publication settings": "Настройки публикации", "Publications": "Публикации", "PublicationsWithCount": "{count, plural, =0 {нет публикаций} one {{count} публикация} few {{count} публикации} other {{count} публикаций}}", + "FollowersWithCount": "{count, plural, =0 {нет подписчиков} one {{count} подписчик} few {{count} подписчика} other {{count} подписчиков}}", + "Publish": "Опубликовать", "Publish Album": "Опубликовать альбом", "Publish Settings": "Настройки публикации", - "Publish": "Опубликовать", "Published": "Опубликованные", "Punchline": "Панчлайн", "Quit": "Выйти", diff --git a/src/components/Article/Comment/Comment.tsx b/src/components/Article/Comment/Comment.tsx index d6af579f..d22a8da7 100644 --- a/src/components/Article/Comment/Comment.tsx +++ b/src/components/Article/Comment/Comment.tsx @@ -38,12 +38,17 @@ export const Comment = (props: Props) => { const [loading, setLoading] = createSignal(false) const [editMode, setEditMode] = createSignal(false) const [clearEditor, setClearEditor] = createSignal(false) - const { author } = useSession() + const { author, session } = useSession() const { createReaction, deleteReaction, updateReaction } = useReactions() const { showConfirm } = useConfirm() const { showSnackbar } = useSnackbar() - const isCommentAuthor = createMemo(() => props.comment.created_by?.slug === author()?.slug) + const canEdit = createMemo( + () => + Boolean(author()?.id) && + (props.comment?.created_by?.id === author().id || session()?.user?.roles.includes('editor')), + ) + const comment = createMemo(() => props.comment) const body = createMemo(() => (comment().body || '').trim()) @@ -93,7 +98,8 @@ export const Comment = (props: Props) => { const handleUpdate = async (value) => { setLoading(true) try { - await updateReaction(props.comment.id, { + await updateReaction({ + id: props.comment.id, kind: ReactionKind.Comment, body: value, shout: props.comment.shout.id, @@ -108,9 +114,7 @@ export const Comment = (props: Props) => { return (
  • props.lastSeen, - })} + class={clsx(styles.comment, props.class, { [styles.isNew]: comment()?.created_at > props.lastSeen })} >
    @@ -189,7 +193,7 @@ export const Comment = (props: Props) => { {loading() ? t('Loading') : t('Reply')} - +
    + + ) +} diff --git a/src/components/AuthorsList/index.ts b/src/components/AuthorsList/index.ts new file mode 100644 index 00000000..4187ebae --- /dev/null +++ b/src/components/AuthorsList/index.ts @@ -0,0 +1 @@ +export { AuthorsList } from './AuthorsList' diff --git a/src/components/Feed/ArticleCard/ArticleCard.tsx b/src/components/Feed/ArticleCard/ArticleCard.tsx index 31743db2..c181cc27 100644 --- a/src/components/Feed/ArticleCard/ArticleCard.tsx +++ b/src/components/Feed/ArticleCard/ArticleCard.tsx @@ -106,7 +106,7 @@ const LAYOUT_ASPECT = { export const ArticleCard = (props: ArticleCardProps) => { const { t, lang, formatDate } = useLocalize() - const { author } = useSession() + const { author, session } = useSession() const { changeSearchParams } = useRouter() const [isActionPopupActive, setIsActionPopupActive] = createSignal(false) const [isCoverImageLoadError, setIsCoverImageLoadError] = createSignal(false) @@ -120,9 +120,13 @@ export const ArticleCard = (props: ArticleCardProps) => { props.article.published_at ? formatDate(new Date(props.article.published_at * 1000)) : '', ) - const canEdit = () => - props.article.authors?.some((a) => a && a?.slug === author()?.slug) || - props.article.created_by?.id === author()?.id + const canEdit = createMemo( + () => + Boolean(author()?.id) && + (props.article?.authors?.some((a) => Boolean(a) && a?.id === author().id) || + props.article?.created_by?.id === author().id || + session()?.user?.roles.includes('editor')), + ) const scrollToComments = (event) => { event.preventDefault() @@ -365,7 +369,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
    props.onShare(props.article)} onInviteClick={props.onInvite} diff --git a/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx b/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx index 2a3bb496..ce9990bb 100644 --- a/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx +++ b/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx @@ -10,7 +10,7 @@ import { SoonChip } from '../../_shared/SoonChip' import styles from './FeedArticlePopup.module.scss' type Props = { - isOwner: boolean + canEdit: boolean onInviteClick: () => void onShareClick: () => void } & Omit @@ -41,7 +41,7 @@ export const FeedArticlePopup = (props: Props) => { {t('Share')}
  • - +
  • - +
  • - {/**/} + {/**/} {/*
  • */} {/* { + const { t } = useLocalize() + return ( +
    +
    + +
    +
    {t('Loading')}
    +
    + ) +} diff --git a/src/components/InlineLoader/index.ts b/src/components/InlineLoader/index.ts new file mode 100644 index 00000000..c94c5a50 --- /dev/null +++ b/src/components/InlineLoader/index.ts @@ -0,0 +1 @@ +export { InlineLoader } from './InlineLoader' diff --git a/src/components/Nav/Header/Link.tsx b/src/components/Nav/Header/Link.tsx index c041c4d0..6bf639b5 100644 --- a/src/components/Nav/Header/Link.tsx +++ b/src/components/Nav/Header/Link.tsx @@ -17,11 +17,11 @@ type Props = { export const Link = (props: Props) => { const { page } = useRouter() - const isSelected = page().route === props.routeName + const isSelected = page()?.route === props.routeName return (
  • { when={props.topic.body} fallback={
    - {t('PublicationsWithCount', { count: props.topic.stat.shouts ?? 0 })} + {t('PublicationsWithCount', { count: props.topic?.stat?.shouts ?? 0 })}
    } > diff --git a/src/components/Views/AllAuthors.tsx b/src/components/Views/AllAuthors.tsx deleted file mode 100644 index 6275621f..00000000 --- a/src/components/Views/AllAuthors.tsx +++ /dev/null @@ -1,234 +0,0 @@ -import type { Author } from '../../graphql/schema/core.gen' - -import { Meta } from '@solidjs/meta' -import { clsx } from 'clsx' -import { For, Show, createEffect, createMemo, createSignal } from 'solid-js' - -import { useFollowing } from '../../context/following' -import { useLocalize } from '../../context/localize' -import { useRouter } from '../../stores/router' -import { loadAuthors, setAuthorsSort, useAuthorsStore } from '../../stores/zine/authors' -import { dummyFilter } from '../../utils/dummyFilter' -import { getImageUrl } from '../../utils/getImageUrl' -import { scrollHandler } from '../../utils/scroll' -import { authorLetterReduce, translateAuthor } from '../../utils/translate' -import { AuthorBadge } from '../Author/AuthorBadge' -import { Loading } from '../_shared/Loading' -import { SearchField } from '../_shared/SearchField' - -import styles from './AllAuthors.module.scss' - -type AllAuthorsPageSearchParams = { - by: '' | 'name' | 'shouts' | 'followers' -} - -type Props = { - authors: Author[] - isLoaded: boolean -} - -const PAGE_SIZE = 20 - -export const AllAuthorsView = (props: Props) => { - const { t, lang } = useLocalize() - const ALPHABET = - lang() === 'ru' ? [...'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ@'] : [...'ABCDEFGHIJKLMNOPQRSTUVWXYZ@'] - const [offsetByShouts, setOffsetByShouts] = createSignal(0) - const [offsetByFollowers, setOffsetByFollowers] = createSignal(0) - const { searchParams, changeSearchParams } = useRouter() - const { sortedAuthors } = useAuthorsStore({ - authors: props.authors, - sortBy: searchParams().by || 'name', - }) - - const [searchQuery, setSearchQuery] = createSignal('') - const offset = searchParams()?.by === 'shouts' ? offsetByShouts : offsetByFollowers - createEffect(() => { - let by = searchParams().by - if (by) { - setAuthorsSort(by) - } else { - by = 'name' - changeSearchParams({ by }) - } - }) - - const loadMoreByShouts = async () => { - await loadAuthors({ by: { order: 'shouts_stat' }, limit: PAGE_SIZE, offset: offsetByShouts() }) - setOffsetByShouts((o) => o + PAGE_SIZE) - } - const loadMoreByFollowers = async () => { - await loadAuthors({ by: { order: 'followers_stat' }, limit: PAGE_SIZE, offset: offsetByFollowers() }) - setOffsetByFollowers((o) => o + PAGE_SIZE) - } - - const isStatsLoaded = createMemo(() => sortedAuthors()?.some((author) => author.stat)) - - createEffect(async () => { - if (!isStatsLoaded()) { - await loadMoreByShouts() - await loadMoreByFollowers() - } - }) - - const showMore = async () => - await { - shouts: loadMoreByShouts, - followers: loadMoreByFollowers, - }[searchParams().by]() - - const byLetter = createMemo<{ [letter: string]: Author[] }>(() => { - return sortedAuthors().reduce( - (acc, author) => authorLetterReduce(acc, author, lang()), - {} as { [letter: string]: Author[] }, - ) - }) - - const { isOwnerSubscribed } = useFollowing() - - const sortedKeys = createMemo(() => { - const keys = Object.keys(byLetter()) - keys.sort() - keys.push(keys.shift()) - return keys - }) - - const filteredAuthors = createMemo(() => { - return dummyFilter(sortedAuthors(), searchQuery(), lang()) - }) - - const ogImage = getImageUrl('production/image/logo_image.png') - const ogTitle = t('Authors') - const description = t('List of authors of the open editorial community') - - return ( -
    - - - - - - - - - - - }> -
    -
    -
    -

    {t('Authors')}

    -

    {t('Subscribe who you like to tune your personal feed')}

    - - - -
    -
    - - 0}> - - - - - {(letter) => ( -
    -

    {letter}

    -
    -
    -
    -
    - - {(author) => ( -
    -
    - {translateAuthor(author, lang())} - - {author.stat.shouts} - -
    -
    - )} -
    -
    -
    -
    -
    -
    - )} -
    -
    - - - - {(author) => ( -
    -
    - -
    -
    - )} -
    -
    - - PAGE_SIZE + offset() && searchParams().by !== 'name'}> -
    -
    - -
    -
    -
    -
    -
    -
    -
    - ) -} diff --git a/src/components/Views/AllAuthors.module.scss b/src/components/Views/AllAuthors/AllAuthors.module.scss similarity index 99% rename from src/components/Views/AllAuthors.module.scss rename to src/components/Views/AllAuthors/AllAuthors.module.scss index 94d4302c..63188b2b 100644 --- a/src/components/Views/AllAuthors.module.scss +++ b/src/components/Views/AllAuthors/AllAuthors.module.scss @@ -81,3 +81,5 @@ overflow-x: auto; } } + + diff --git a/src/components/Views/AllAuthors/AllAuthors.tsx b/src/components/Views/AllAuthors/AllAuthors.tsx new file mode 100644 index 00000000..c1723891 --- /dev/null +++ b/src/components/Views/AllAuthors/AllAuthors.tsx @@ -0,0 +1,177 @@ +import type { Author } from '../../../graphql/schema/core.gen' + +import { Meta } from '@solidjs/meta' +import { clsx } from 'clsx' +import { For, Show, createEffect, createMemo, createSignal } from 'solid-js' + +import { useLocalize } from '../../../context/localize' +import { useRouter } from '../../../stores/router' +import { setAuthorsSort, useAuthorsStore } from '../../../stores/zine/authors' +import { getImageUrl } from '../../../utils/getImageUrl' +import { scrollHandler } from '../../../utils/scroll' +import { authorLetterReduce, translateAuthor } from '../../../utils/translate' + +import { AuthorsList } from '../../AuthorsList' +import { Loading } from '../../_shared/Loading' +import { SearchField } from '../../_shared/SearchField' + +import styles from './AllAuthors.module.scss' + +type AllAuthorsPageSearchParams = { + by: '' | 'name' | 'shouts' | 'followers' +} + +type Props = { + authors: Author[] + isLoaded: boolean +} + +export const AllAuthors = (props: Props) => { + const { t, lang } = useLocalize() + const ALPHABET = + lang() === 'ru' ? [...'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ@'] : [...'ABCDEFGHIJKLMNOPQRSTUVWXYZ@'] + const { searchParams, changeSearchParams } = useRouter() + const { sortedAuthors } = useAuthorsStore({ + authors: props.authors, + sortBy: searchParams().by || 'name', + }) + + const [searchQuery, setSearchQuery] = createSignal('') + + createEffect(() => { + let by = searchParams().by + if (by) { + setAuthorsSort(by) + } else { + by = 'name' + changeSearchParams({ by }) + } + }) + + const byLetter = createMemo<{ [letter: string]: Author[] }>(() => { + return sortedAuthors().reduce( + (acc, author) => authorLetterReduce(acc, author, lang()), + {} as { [letter: string]: Author[] }, + ) + }) + + const sortedKeys = createMemo(() => { + const keys = Object.keys(byLetter()) + keys.sort() + keys.push(keys.shift()) + return keys + }) + + const ogImage = getImageUrl('production/image/logo_image.png') + const ogTitle = t('Authors') + const description = t('List of authors of the open editorial community') + + return ( +
    + + + + + + + + + + + }> +
    +
    +
    +

    {t('Authors')}

    +

    {t('Subscribe who you like to tune your personal feed')}

    + +
    +
    + + + + + {(letter) => ( +
    +

    {letter}

    +
    +
    +
    +
    + + {(author) => ( +
    +
    + {translateAuthor(author, lang())} + + {author.stat.shouts} + +
    +
    + )} +
    +
    +
    +
    +
    +
    + )} +
    +
    + }> + + +
    +
    +
    + ) +} diff --git a/src/components/Views/AllAuthors/index.ts b/src/components/Views/AllAuthors/index.ts new file mode 100644 index 00000000..13e92537 --- /dev/null +++ b/src/components/Views/AllAuthors/index.ts @@ -0,0 +1 @@ +export { AllAuthors } from './AllAuthors' diff --git a/src/components/Views/Author/Author.tsx b/src/components/Views/Author/Author.tsx index bb62322c..5bcd6ba8 100644 --- a/src/components/Views/Author/Author.tsx +++ b/src/components/Views/Author/Author.tsx @@ -71,13 +71,7 @@ export const AuthorView = (props: Props) => { const fetchData = async (slug) => { try { const [subscriptionsResult, followersResult] = await Promise.all([ - (async () => { - const [getAuthors, getTopics] = await Promise.all([ - apiClient.getAuthorFollowingAuthors({ slug }), - apiClient.getAuthorFollowingTopics({ slug }), - ]) - return { authors: getAuthors, topics: getTopics } - })(), + apiClient.getAuthorFollows({ slug }), apiClient.getAuthorFollowers({ slug }), ]) diff --git a/src/components/Views/Home.tsx b/src/components/Views/Home.tsx index 200434b5..4e4dbce3 100644 --- a/src/components/Views/Home.tsx +++ b/src/components/Views/Home.tsx @@ -69,10 +69,12 @@ export const HomeView = (props: Props) => { } const result = await apiClient.getRandomTopicShouts(RANDOM_TOPIC_SHOUTS_COUNT) - if (!result) console.warn('[apiClient.getRandomTopicShouts] failed') + if (!result || result.error) console.warn('[apiClient.getRandomTopicShouts] failed') batch(() => { - if (result?.topic) setRandomTopic(result.topic) - if (result?.shouts) setRandomTopicArticles(result.shouts) + if (!result?.error) { + if (result?.topic) setRandomTopic(result.topic) + if (result?.shouts) setRandomTopicArticles(result.shouts) + } }) }) diff --git a/src/components/Views/ProfileSubscriptions/ProfileSubscriptions.tsx b/src/components/Views/ProfileSubscriptions/ProfileSubscriptions.tsx index 8c342356..6e1b4d8d 100644 --- a/src/components/Views/ProfileSubscriptions/ProfileSubscriptions.tsx +++ b/src/components/Views/ProfileSubscriptions/ProfileSubscriptions.tsx @@ -29,12 +29,9 @@ export const ProfileSubscriptions = () => { const fetchSubscriptions = async () => { try { const slug = author()?.slug - const [getAuthors, getTopics] = await Promise.all([ - apiClient.getAuthorFollowingAuthors({ slug }), - apiClient.getAuthorFollowingTopics({ slug }), - ]) - setFollowing([...getAuthors, ...getTopics]) - setFiltered([...getAuthors, ...getTopics]) + const authorFollows = await apiClient.getAuthorFollows({ slug }) + setFollowing([...authorFollows['authors']]) + setFiltered([...authorFollows['authors'], ...authorFollows['topics']]) } catch (error) { console.error('[fetchSubscriptions] :', error) throw error diff --git a/src/components/_shared/InviteMembers/InviteMembers.module.scss b/src/components/_shared/InviteMembers/InviteMembers.module.scss index 8710a65a..0e9f8964 100644 --- a/src/components/_shared/InviteMembers/InviteMembers.module.scss +++ b/src/components/_shared/InviteMembers/InviteMembers.module.scss @@ -50,24 +50,6 @@ } } - .loading { - @include font-size(1.4rem); - - display: flex; - align-items: center; - justify-content: center; - gap: 1rem; - width: 100%; - flex-direction: row; - opacity: 0.5; - - .icon { - position: relative; - width: 18px; - height: 18px; - } - } - .teaser { min-height: 300px; display: flex; diff --git a/src/components/_shared/InviteMembers/InviteMembers.tsx b/src/components/_shared/InviteMembers/InviteMembers.tsx index 0458c7a5..3eca7cdc 100644 --- a/src/components/_shared/InviteMembers/InviteMembers.tsx +++ b/src/components/_shared/InviteMembers/InviteMembers.tsx @@ -12,6 +12,7 @@ import { Button } from '../Button' import { DropdownSelect } from '../DropdownSelect' import { Loading } from '../Loading' +import { InlineLoader } from '../../InlineLoader' import styles from './InviteMembers.module.scss' type InviteAuthor = Author & { selected: boolean } @@ -62,7 +63,7 @@ export const InviteMembers = (props: Props) => { return authors?.slice(start, end) } - const [pages, _infiniteScrollLoader, { end }] = createInfiniteScroll(fetcher) + const [pages, setEl, { end }] = createInfiniteScroll(fetcher) createEffect( on( @@ -158,11 +159,8 @@ export const InviteMembers = (props: Props) => { )} -
    -
    - -
    -
    {t('Loading')}
    +
    void}> +
    diff --git a/src/context/following.tsx b/src/context/following.tsx index b7a5ab0d..f3208ca9 100644 --- a/src/context/following.tsx +++ b/src/context/following.tsx @@ -2,20 +2,14 @@ import { Accessor, JSX, createContext, createEffect, createSignal, useContext } import { createStore } from 'solid-js/store' import { apiClient } from '../graphql/client/core' -import { Author, Community, FollowingEntity, Topic } from '../graphql/schema/core.gen' +import { AuthorFollows, FollowingEntity } from '../graphql/schema/core.gen' import { useSession } from './session' -type SubscriptionsData = { - topics?: Topic[] - authors?: Author[] - communities?: Community[] -} - interface FollowingContextType { loading: Accessor - subscriptions: SubscriptionsData - setSubscriptions: (subscriptions: SubscriptionsData) => void + subscriptions: AuthorFollows + setSubscriptions: (subscriptions: AuthorFollows) => void setFollowing: (what: FollowingEntity, slug: string, value: boolean) => void loadSubscriptions: () => void follow: (what: FollowingEntity, slug: string) => Promise @@ -29,7 +23,7 @@ export function useFollowing() { return useContext(FollowingContext) } -const EMPTY_SUBSCRIPTIONS = { +const EMPTY_SUBSCRIPTIONS: AuthorFollows = { topics: [], authors: [], communities: [], @@ -37,15 +31,15 @@ const EMPTY_SUBSCRIPTIONS = { export const FollowingProvider = (props: { children: JSX.Element }) => { const [loading, setLoading] = createSignal(false) - const [subscriptions, setSubscriptions] = createStore(EMPTY_SUBSCRIPTIONS) - const { author } = useSession() + const [subscriptions, setSubscriptions] = createStore(EMPTY_SUBSCRIPTIONS) + const { author, session } = useSession() const fetchData = async () => { setLoading(true) try { if (apiClient.private) { console.debug('[context.following] fetching subs data...') - const result = await apiClient.getMySubscriptions() + const result = await apiClient.getAuthorFollows({ user: session()?.user.id }) setSubscriptions(result || EMPTY_SUBSCRIPTIONS) console.info('[context.following] subs:', subscriptions) } diff --git a/src/context/reactions.tsx b/src/context/reactions.tsx index 48da0594..ae4c7b95 100644 --- a/src/context/reactions.tsx +++ b/src/context/reactions.tsx @@ -18,7 +18,7 @@ type ReactionsContextType = { offset?: number }) => Promise createReaction: (reaction: ReactionInput) => Promise - updateReaction: (id: number, reaction: ReactionInput) => Promise + updateReaction: (reaction: ReactionInput) => Promise deleteReaction: (id: number) => Promise } @@ -88,8 +88,8 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => { } } - const updateReaction = async (id: number, input: ReactionInput): Promise => { - const reaction = await apiClient.updateReaction(id, input) + const updateReaction = async (input: ReactionInput): Promise => { + const reaction = await apiClient.updateReaction(input) setReactionEntities(reaction.id, reaction) } diff --git a/src/graphql/client/core.ts b/src/graphql/client/core.ts index c80fe931..d9fef87a 100644 --- a/src/graphql/client/core.ts +++ b/src/graphql/client/core.ts @@ -1,7 +1,7 @@ import type { Author, + AuthorFollows, CommonResult, - Community, FollowingEntity, LoadShoutsOptions, MutationDelete_ShoutArgs, @@ -37,16 +37,14 @@ import shoutsLoadSearch from '../query/core/articles-load-search' import loadShoutsUnrated from '../query/core/articles-load-unrated' import authorBy from '../query/core/author-by' import authorFollowers from '../query/core/author-followers' +import authorFollows from '../query/core/author-follows' import authorId from '../query/core/author-id' import authorsAll from '../query/core/authors-all' -import authorFollowedAuthors from '../query/core/authors-followed-by' import authorsLoadBy from '../query/core/authors-load-by' -import authorFollowedCommunities from '../query/core/communities-followed-by' import mySubscriptions from '../query/core/my-followed' import reactionsLoadBy from '../query/core/reactions-load-by' import topicBySlug from '../query/core/topic-by-slug' import topicsAll from '../query/core/topics-all' -import authorFollowedTopics from '../query/core/topics-followed-by' import topicsRandomQuery from '../query/core/topics-random' const publicGraphQLClient = createGraphQLClient('core') @@ -86,7 +84,7 @@ export const apiClient = { return response.data.get_topics_random }, - getRandomTopicShouts: async (limit: number): Promise<{ topic: Topic; shouts: Shout[] }> => { + getRandomTopicShouts: async (limit: number): Promise => { const resp = await publicGraphQLClient.query(articlesLoadRandomTopic, { limit }).toPromise() if (!resp.data) console.error('[graphql.client.core] load_shouts_random_topic', resp) return resp.data.load_shouts_random_topic @@ -96,6 +94,7 @@ export const apiClient = { const response = await apiClient.private.mutation(followMutation, { what, slug }).toPromise() return response.data.follow }, + unfollow: async ({ what, slug }: { what: FollowingEntity; slug: string }) => { const response = await apiClient.private.mutation(unfollowMutation, { what, slug }).toPromise() return response.data.unfollow @@ -107,48 +106,53 @@ export const apiClient = { return response.data.get_topics_all }, + getAllAuthors: async () => { const response = await publicGraphQLClient.query(authorsAll, {}).toPromise() if (!response.data) console.error('[graphql.client.core] getAllAuthors', response) return response.data.get_authors_all }, + getAuthor: async (params: { slug?: string; author_id?: number }): Promise => { const response = await publicGraphQLClient.query(authorBy, params).toPromise() return response.data.get_author }, + getAuthorId: async (params: { user: string }): Promise => { const response = await publicGraphQLClient.query(authorId, params).toPromise() return response.data.get_author_id }, + getAuthorFollowers: async ({ slug }: { slug: string }): Promise => { const response = await publicGraphQLClient.query(authorFollowers, { slug }).toPromise() return response.data.get_author_followers }, - getAuthorFollowingAuthors: async ({ slug }: { slug: string }): Promise => { - const response = await publicGraphQLClient.query(authorFollowedAuthors, { slug }).toPromise() - return response.data.get_author_followed - }, - getAuthorFollowingTopics: async ({ slug }: { slug: string }): Promise => { - const response = await publicGraphQLClient.query(authorFollowedTopics, { slug }).toPromise() - return response.data.get_topics_by_author - }, - getAuthorFollowingCommunities: async ({ slug }: { slug: string }): Promise => { - const response = await publicGraphQLClient.query(authorFollowedCommunities, { slug }).toPromise() - return response.data.get_communities_by_author + + getAuthorFollows: async (params: { + slug?: string + author_id?: number + user?: string + }): Promise => { + const response = await publicGraphQLClient.query(authorFollows, params).toPromise() + return response.data.get_author_follows }, + updateAuthor: async (input: ProfileInput) => { const response = await apiClient.private.mutation(updateAuthor, { profile: input }).toPromise() return response.data.update_author }, + getTopic: async ({ slug }: { slug: string }): Promise => { const response = await publicGraphQLClient.query(topicBySlug, { slug }).toPromise() return response.data.get_topic }, + createArticle: async ({ article }: { article: ShoutInput }): Promise => { const response = await apiClient.private.mutation(createArticle, { shout: article }).toPromise() return response.data.create_shout.shout }, + updateArticle: async ({ shout_id, shout_input, @@ -164,10 +168,12 @@ export const apiClient = { console.debug('[graphql.client.core] updateArticle:', response.data) return response.data.update_shout.shout }, + deleteShout: async (params: MutationDelete_ShoutArgs): Promise => { const response = await apiClient.private.mutation(deleteShout, params).toPromise() console.debug('[graphql.client.core] deleteShout:', response) }, + getDrafts: async (): Promise => { const response = await apiClient.private.query(draftsLoad, {}).toPromise() console.debug('[graphql.client.core] getDrafts:', response) @@ -178,15 +184,13 @@ export const apiClient = { console.debug('[graphql.client.core] createReaction:', response) return response.data.create_reaction.reaction }, - destroyReaction: async (id: number) => { - const response = await apiClient.private.mutation(reactionDestroy, { id: id }).toPromise() + destroyReaction: async (reaction_id: number) => { + const response = await apiClient.private.mutation(reactionDestroy, { reaction_id }).toPromise() console.debug('[graphql.client.core] destroyReaction:', response) return response.data.delete_reaction.reaction }, - updateReaction: async (id: number, input: ReactionInput) => { - const response = await apiClient.private - .mutation(reactionUpdate, { id: id, reaction: input }) - .toPromise() + updateReaction: async (reaction: ReactionInput) => { + const response = await apiClient.private.mutation(reactionUpdate, { reaction }).toPromise() console.debug('[graphql.client.core] updateReaction:', response) return response.data.update_reaction.reaction }, @@ -233,9 +237,4 @@ export const apiClient = { .toPromise() return resp.data.load_reactions_by }, - getMySubscriptions: async (): Promise => { - const resp = await apiClient.private.query(mySubscriptions, {}).toPromise() - - return resp.data.get_my_followed - }, } diff --git a/src/graphql/mutation/core/reaction-update.ts b/src/graphql/mutation/core/reaction-update.ts index 03c32a04..6c20be49 100644 --- a/src/graphql/mutation/core/reaction-update.ts +++ b/src/graphql/mutation/core/reaction-update.ts @@ -1,8 +1,8 @@ import { gql } from '@urql/core' export default gql` - mutation UpdateReactionMutation($id: Int!, $reaction: ReactionInput!) { - update_reaction(id: $id, reaction: $reaction) { + mutation UpdateReactionMutation($reaction: ReactionInput!) { + update_reaction(reaction: $reaction) { error reaction { id diff --git a/src/graphql/query/core/author-follows.ts b/src/graphql/query/core/author-follows.ts new file mode 100644 index 00000000..b1c8c0ce --- /dev/null +++ b/src/graphql/query/core/author-follows.ts @@ -0,0 +1,20 @@ +import { gql } from '@urql/core' + +export default gql` + query GetAuthorFollows($slug: String, $user: String, $author_id: Int) { + get_author_follows(slug: $slug, user: $user, author_id: $author_id) { + authors { + id + slug + name + pic + bio + } + topics { + id + slug + title + } + } + } +` diff --git a/src/graphql/query/core/authors-followed-by.ts b/src/graphql/query/core/authors-followed-by.ts deleted file mode 100644 index 06f6f5e9..00000000 --- a/src/graphql/query/core/authors-followed-by.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { gql } from '@urql/core' - -export default gql` - query AuthorsFollowedByQuery($slug: String, $user: String, $author_id: Int) { - get_author_followed(slug: $slug, user: $user, author_id: $author_id) { - id - slug - name - pic - bio - created_at - stat { - shouts - } - } - } -` diff --git a/src/pages/allAuthors.page.tsx b/src/pages/allAuthors.page.tsx index 87a427b2..7079e8af 100644 --- a/src/pages/allAuthors.page.tsx +++ b/src/pages/allAuthors.page.tsx @@ -1,8 +1,8 @@ import type { PageProps } from './types' -import { createSignal, onMount } from 'solid-js' +import { createEffect, createSignal, onMount } from 'solid-js' -import { AllAuthorsView } from '../components/Views/AllAuthors' +import { AllAuthors } from '../components/Views/AllAuthors/' import { PageLayout } from '../components/_shared/PageLayout' import { useLocalize } from '../context/localize' import { loadAllAuthors } from '../stores/zine/authors' @@ -23,7 +23,7 @@ export const AllAuthorsPage = (props: PageProps) => { return ( - + ) } diff --git a/src/stores/router.ts b/src/stores/router.ts index be197bd1..2533b252 100644 --- a/src/stores/router.ts +++ b/src/stores/router.ts @@ -153,7 +153,7 @@ export const useRouter = = Record< } const clearSearchParams = (replace = false) => { - searchParamsStore.open({}, replace) + // searchParamsStore.open({}, replace) } return { diff --git a/src/stores/zine/authors.ts b/src/stores/zine/authors.ts index db3f0627..c046737a 100644 --- a/src/stores/zine/authors.ts +++ b/src/stores/zine/authors.ts @@ -6,6 +6,7 @@ import { Author, QueryLoad_Authors_ByArgs } from '../../graphql/schema/core.gen' import { byStat } from '../../utils/sortby' export type AuthorsSortBy = 'shouts' | 'name' | 'followers' +type SortedAuthorsSetter = (prev: Author[]) => Author[] const [sortAllBy, setSortAllBy] = createSignal('name') @@ -13,6 +14,11 @@ export const setAuthorsSort = (sortBy: AuthorsSortBy) => setSortAllBy(sortBy) const [authorEntities, setAuthorEntities] = createSignal<{ [authorSlug: string]: Author }>({}) const [authorsByTopic, setAuthorsByTopic] = createSignal<{ [topicSlug: string]: Author[] }>({}) +const [authorsByShouts, setSortedAuthorsByShout] = createSignal([]) +const [authorsByFollowers, setSortedAuthorsByFollowers] = createSignal([]) + +export const setAuthorsByShouts = (authors: SortedAuthorsSetter) => setSortedAuthorsByShout(authors) +export const setAuthorsByFollowers = (authors: SortedAuthorsSetter) => setSortedAuthorsByFollowers(authors) const sortedAuthors = createLazyMemo(() => { const authors = Object.values(authorEntities()) @@ -108,5 +114,5 @@ export const useAuthorsStore = (initialState: InitialState = {}) => { } addAuthors([...(initialState.authors || [])]) - return { authorEntities, sortedAuthors, authorsByTopic } + return { authorEntities, sortedAuthors, authorsByTopic, authorsByShouts, authorsByFollowers } }