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/Author/AuthorBadge/AuthorBadge.module.scss b/src/components/Author/AuthorBadge/AuthorBadge.module.scss
index e78f10ca..8dc68f4b 100644
--- a/src/components/Author/AuthorBadge/AuthorBadge.module.scss
+++ b/src/components/Author/AuthorBadge/AuthorBadge.module.scss
@@ -58,6 +58,11 @@
}
.bio {
+ @include font-size(1.2rem);
+
+ display: flex;
+ flex-direction: row;
+ gap: 1rem;
color: var(--black-400);
font-weight: 500;
}
diff --git a/src/components/Author/AuthorBadge/AuthorBadge.tsx b/src/components/Author/AuthorBadge/AuthorBadge.tsx
index 063cf8f7..e0ef0334 100644
--- a/src/components/Author/AuthorBadge/AuthorBadge.tsx
+++ b/src/components/Author/AuthorBadge/AuthorBadge.tsx
@@ -118,12 +118,17 @@ export const AuthorBadge = (props: Props) => {
- 0}>
-
- {t('PublicationsWithCount', { count: props.author?.stat.shouts ?? 0 })}
-
-
+
+
+
0}>
+ {t('PublicationsWithCount', { count: props.author.stat?.shouts ?? 0 })}
+
+
0}>
+ {t('FollowersWithCount', { count: props.author.stat?.followers ?? 0 })}
+
+
+
diff --git a/src/components/AuthorsList/AuthorsList.module.scss b/src/components/AuthorsList/AuthorsList.module.scss
new file mode 100644
index 00000000..bad088be
--- /dev/null
+++ b/src/components/AuthorsList/AuthorsList.module.scss
@@ -0,0 +1,26 @@
+.AuthorsList {
+ .action {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 8rem;
+ }
+
+ .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;
+ }
+ }
+}
diff --git a/src/components/AuthorsList/AuthorsList.tsx b/src/components/AuthorsList/AuthorsList.tsx
new file mode 100644
index 00000000..236a586e
--- /dev/null
+++ b/src/components/AuthorsList/AuthorsList.tsx
@@ -0,0 +1,90 @@
+import { clsx } from 'clsx'
+import { For, Show, createEffect, createSignal } from 'solid-js'
+import { useFollowing } from '../../context/following'
+import { useLocalize } from '../../context/localize'
+import { apiClient } from '../../graphql/client/core'
+import { setAuthorsByFollowers, setAuthorsByShouts, useAuthorsStore } from '../../stores/zine/authors'
+import { AuthorBadge } from '../Author/AuthorBadge'
+import { InlineLoader } from '../InlineLoader'
+import { Button } from '../_shared/Button'
+import styles from './AuthorsList.module.scss'
+
+type Props = {
+ class?: string
+ query: 'shouts' | 'followers'
+}
+
+const PAGE_SIZE = 20
+export const AuthorsList = (props: Props) => {
+ const { t } = useLocalize()
+ const { isOwnerSubscribed } = useFollowing()
+ const [loading, setLoading] = createSignal(false)
+ const [currentPage, setCurrentPage] = createSignal({ shouts: 0, followers: 0 })
+ const { authorsByShouts, authorsByFollowers } = useAuthorsStore()
+
+ const fetchAuthors = async (queryType: 'shouts' | 'followers', page: number) => {
+ setLoading(true)
+ const offset = PAGE_SIZE * page
+ const result = await apiClient.loadAuthorsBy({
+ by: { order: queryType },
+ limit: PAGE_SIZE,
+ offset: offset,
+ })
+
+ if (queryType === 'shouts') {
+ setAuthorsByShouts((prev) => [...prev, ...result])
+ } else {
+ setAuthorsByFollowers((prev) => [...prev, ...result])
+ }
+ setLoading(false)
+ return result
+ }
+
+ const loadMoreAuthors = () => {
+ const queryType = props.query
+ const nextPage = currentPage()[queryType] + 1
+ fetchAuthors(queryType, nextPage).then(() =>
+ setCurrentPage({ ...currentPage(), [queryType]: nextPage }),
+ )
+ }
+
+ createEffect(() => {
+ const queryType = props.query
+ if (
+ currentPage()[queryType] === 0 &&
+ (authorsByShouts().length === 0 || authorsByFollowers().length === 0)
+ ) {
+ loadMoreAuthors()
+ }
+ })
+
+ const authorsList = () => (props.query === 'shouts' ? authorsByShouts() : authorsByFollowers())
+
+ return (
+
+
+ {(author) => (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ )
+}
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/InlineLoader/InlineLoader.module.scss b/src/components/InlineLoader/InlineLoader.module.scss
new file mode 100644
index 00000000..dc90c7bd
--- /dev/null
+++ b/src/components/InlineLoader/InlineLoader.module.scss
@@ -0,0 +1,18 @@
+.InlineLoader {
+ @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;
+ }
+
+}
diff --git a/src/components/InlineLoader/InlineLoader.tsx b/src/components/InlineLoader/InlineLoader.tsx
new file mode 100644
index 00000000..6f36ff4e
--- /dev/null
+++ b/src/components/InlineLoader/InlineLoader.tsx
@@ -0,0 +1,20 @@
+import { clsx } from 'clsx'
+import { useLocalize } from '../../context/localize'
+import { Loading } from '../_shared/Loading'
+import styles from './InlineLoader.module.scss'
+
+type Props = {
+ class?: string
+}
+
+export const InlineLoader = (props: Props) => {
+ const { t } = useLocalize()
+ return (
+
+ )
+}
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/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) => (
-
- )}
-
-
-
-
-
-
- )}
-
-
-
-
-
- {(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) => (
+
+ )}
+
+
+
+
+
+
+ )}
+
+
+
}>
+
+
+
+
+
+ )
+}
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/_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/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/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 }
}