2023-11-28 13:18:25 +00:00
|
|
|
|
import type { Author } from '../../graphql/schema/core.gen'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
|
2023-12-13 10:39:31 +00:00
|
|
|
|
import { Meta } from '@solidjs/meta'
|
2022-11-09 19:02:12 +00:00
|
|
|
|
import { clsx } from 'clsx'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
import { createEffect, createMemo, createSignal, For, Show } from 'solid-js'
|
|
|
|
|
|
2024-02-05 12:34:47 +00:00
|
|
|
|
import { useFollowing } from '../../context/following'
|
2023-02-17 09:21:02 +00:00
|
|
|
|
import { useLocalize } from '../../context/localize'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
import { useRouter } from '../../stores/router'
|
2023-12-27 20:34:41 +00:00
|
|
|
|
import { loadAuthors, setAuthorsSort, useAuthorsStore } from '../../stores/zine/authors'
|
2023-12-31 05:01:34 +00:00
|
|
|
|
import { capitalize } from '../../utils/capitalize'
|
|
|
|
|
import { isCyrillic } from '../../utils/cyrillic'
|
2023-09-18 16:33:22 +00:00
|
|
|
|
import { dummyFilter } from '../../utils/dummyFilter'
|
2023-12-13 10:39:31 +00:00
|
|
|
|
import { getImageUrl } from '../../utils/getImageUrl'
|
2023-12-31 05:01:34 +00:00
|
|
|
|
import { translit } from '../../utils/ru2en'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
import { scrollHandler } from '../../utils/scroll'
|
2023-12-13 10:39:31 +00:00
|
|
|
|
import { Loading } from '../_shared/Loading'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
import { SearchField } from '../_shared/SearchField'
|
2023-10-20 16:21:40 +00:00
|
|
|
|
import { AuthorBadge } from '../Author/AuthorBadge'
|
2022-09-09 11:53:35 +00:00
|
|
|
|
|
2023-10-18 10:56:41 +00:00
|
|
|
|
import styles from './AllAuthors.module.scss'
|
|
|
|
|
|
2022-09-22 09:37:49 +00:00
|
|
|
|
type AllAuthorsPageSearchParams = {
|
2022-11-22 09:27:01 +00:00
|
|
|
|
by: '' | 'name' | 'shouts' | 'followers'
|
2022-09-22 09:37:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-12-13 10:39:31 +00:00
|
|
|
|
type Props = {
|
2022-09-22 09:37:49 +00:00
|
|
|
|
authors: Author[]
|
2023-12-13 10:39:31 +00:00
|
|
|
|
isLoaded: boolean
|
2022-09-22 09:37:49 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-11-13 19:35:57 +00:00
|
|
|
|
const PAGE_SIZE = 20
|
|
|
|
|
|
2023-12-13 10:39:31 +00:00
|
|
|
|
export const AllAuthorsView = (props: Props) => {
|
2023-02-17 09:21:02 +00:00
|
|
|
|
const { t, lang } = useLocalize()
|
2023-12-28 00:30:09 +00:00
|
|
|
|
const ALPHABET =
|
|
|
|
|
lang() === 'ru' ? [...'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ@'] : [...'ABCDEFGHIJKLMNOPQRSTUVWXYZ@']
|
2023-12-27 20:34:41 +00:00
|
|
|
|
const [offsetByShouts, setOffsetByShouts] = createSignal(0)
|
|
|
|
|
const [offsetByFollowers, setOffsetByFollowers] = createSignal(0)
|
2023-12-20 08:07:57 +00:00
|
|
|
|
const { searchParams, changeSearchParams } = useRouter<AllAuthorsPageSearchParams>()
|
2022-11-22 09:27:01 +00:00
|
|
|
|
const { sortedAuthors } = useAuthorsStore({
|
|
|
|
|
authors: props.authors,
|
2023-12-24 17:29:16 +00:00
|
|
|
|
sortBy: searchParams().by || 'name',
|
2022-11-22 09:27:01 +00:00
|
|
|
|
})
|
2022-09-30 14:22:33 +00:00
|
|
|
|
|
2022-12-01 18:45:35 +00:00
|
|
|
|
const [searchQuery, setSearchQuery] = createSignal('')
|
2023-12-27 20:34:41 +00:00
|
|
|
|
const offset = searchParams()?.by === 'shouts' ? offsetByShouts : offsetByFollowers
|
2023-11-02 17:43:22 +00:00
|
|
|
|
createEffect(() => {
|
2023-12-26 20:33:45 +00:00
|
|
|
|
let by = searchParams().by
|
|
|
|
|
if (by) {
|
|
|
|
|
setAuthorsSort(by)
|
|
|
|
|
} else {
|
|
|
|
|
by = 'name'
|
|
|
|
|
changeSearchParams({ by })
|
2022-11-25 05:54:19 +00:00
|
|
|
|
}
|
|
|
|
|
})
|
2022-12-01 18:45:35 +00:00
|
|
|
|
|
2023-12-27 20:34:41 +00:00
|
|
|
|
const loadMoreByShouts = async () => {
|
2023-12-27 22:01:42 +00:00
|
|
|
|
await loadAuthors({ by: { order: 'shouts_stat' }, limit: PAGE_SIZE, offset: offsetByShouts() })
|
2023-12-27 20:34:41 +00:00
|
|
|
|
setOffsetByShouts((o) => o + PAGE_SIZE)
|
|
|
|
|
}
|
|
|
|
|
const loadMoreByFollowers = async () => {
|
2023-12-27 22:01:42 +00:00
|
|
|
|
await loadAuthors({ by: { order: 'followers_stat' }, limit: PAGE_SIZE, offset: offsetByFollowers() })
|
2023-12-27 20:34:41 +00:00
|
|
|
|
setOffsetByFollowers((o) => o + PAGE_SIZE)
|
2023-12-26 20:33:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isStatsLoaded = createMemo(() => sortedAuthors() && sortedAuthors().some((author) => author.stat))
|
|
|
|
|
|
|
|
|
|
createEffect(async () => {
|
|
|
|
|
if (!isStatsLoaded()) {
|
2023-12-27 20:34:41 +00:00
|
|
|
|
await loadMoreByShouts()
|
|
|
|
|
await loadMoreByFollowers()
|
2023-12-26 20:33:45 +00:00
|
|
|
|
}
|
2022-10-05 15:56:59 +00:00
|
|
|
|
})
|
|
|
|
|
|
2023-12-27 20:34:41 +00:00
|
|
|
|
const showMore = async () =>
|
|
|
|
|
await {
|
|
|
|
|
shouts: loadMoreByShouts,
|
|
|
|
|
followers: loadMoreByFollowers,
|
|
|
|
|
}[searchParams().by]()
|
2023-12-28 00:30:09 +00:00
|
|
|
|
const translate = (author: Author) =>
|
|
|
|
|
lang() === 'en' && isCyrillic(author.name)
|
2023-12-28 00:52:54 +00:00
|
|
|
|
? capitalize(translit(author.name.replace(/ё/, 'e').replace(/ь/, '')).replace(/-/, ' '), true)
|
2023-12-28 00:30:09 +00:00
|
|
|
|
: author.name
|
2022-10-05 15:56:59 +00:00
|
|
|
|
const byLetter = createMemo<{ [letter: string]: Author[] }>(() => {
|
2023-11-04 16:09:29 +00:00
|
|
|
|
return sortedAuthors().reduce(
|
|
|
|
|
(acc, author) => {
|
|
|
|
|
let letter = ''
|
2023-12-28 00:30:09 +00:00
|
|
|
|
if (!letter && author && author.name) {
|
|
|
|
|
const name = translate(author)
|
2023-12-31 05:01:34 +00:00
|
|
|
|
.replace(/[^\dA-zА-я]/, ' ')
|
2023-12-28 00:52:54 +00:00
|
|
|
|
.trim()
|
2023-12-28 00:30:09 +00:00
|
|
|
|
const nameParts = name.trim().split(' ')
|
|
|
|
|
const found = nameParts.filter(Boolean).pop()
|
|
|
|
|
if (found && found.length > 0) {
|
|
|
|
|
letter = found[0].toUpperCase()
|
2023-10-20 16:21:40 +00:00
|
|
|
|
}
|
2023-11-04 16:09:29 +00:00
|
|
|
|
}
|
|
|
|
|
if (/[^ËА-яё]/.test(letter) && lang() === 'ru') letter = '@'
|
2023-12-28 00:30:09 +00:00
|
|
|
|
if (/[^A-z]/.test(letter) && lang() === 'en') letter = '@'
|
2023-10-20 16:21:40 +00:00
|
|
|
|
|
2023-11-04 16:09:29 +00:00
|
|
|
|
if (!acc[letter]) acc[letter] = []
|
2023-12-28 00:52:54 +00:00
|
|
|
|
author.name = translate(author)
|
2023-11-04 16:09:29 +00:00
|
|
|
|
acc[letter].push(author)
|
2023-12-28 00:52:54 +00:00
|
|
|
|
|
|
|
|
|
// Sort authors within each letter group alphabetically by name
|
|
|
|
|
acc[letter].sort((a, b) => a.name.localeCompare(b.name))
|
|
|
|
|
|
2023-11-04 16:09:29 +00:00
|
|
|
|
return acc
|
|
|
|
|
},
|
2023-11-14 15:10:00 +00:00
|
|
|
|
{} as { [letter: string]: Author[] },
|
2023-11-04 16:09:29 +00:00
|
|
|
|
)
|
2022-10-05 15:56:59 +00:00
|
|
|
|
})
|
|
|
|
|
|
2024-02-05 12:34:47 +00:00
|
|
|
|
const { isOwnerSubscribed } = useFollowing()
|
|
|
|
|
|
2022-10-05 15:56:59 +00:00
|
|
|
|
const sortedKeys = createMemo<string[]>(() => {
|
|
|
|
|
const keys = Object.keys(byLetter())
|
|
|
|
|
keys.sort()
|
2022-12-01 18:45:35 +00:00
|
|
|
|
keys.push(keys.shift())
|
2022-10-05 15:56:59 +00:00
|
|
|
|
return keys
|
2022-10-05 11:13:15 +00:00
|
|
|
|
})
|
|
|
|
|
|
2022-12-01 18:45:35 +00:00
|
|
|
|
const filteredAuthors = createMemo(() => {
|
2023-09-18 16:33:22 +00:00
|
|
|
|
return dummyFilter(sortedAuthors(), searchQuery(), lang())
|
2022-12-01 18:45:35 +00:00
|
|
|
|
})
|
2022-11-27 17:02:04 +00:00
|
|
|
|
|
2023-12-13 10:39:31 +00:00
|
|
|
|
const ogImage = getImageUrl('production/image/logo_image.png')
|
|
|
|
|
const ogTitle = t('Authors')
|
|
|
|
|
const description = t('List of authors of the open editorial community')
|
2022-11-27 17:02:04 +00:00
|
|
|
|
|
2022-09-09 11:53:35 +00:00
|
|
|
|
return (
|
2023-10-18 10:56:41 +00:00
|
|
|
|
<div class={clsx(styles.allAuthorsPage, 'wide-container')}>
|
2023-12-13 10:39:31 +00:00
|
|
|
|
<Meta name="descprition" content={description} />
|
|
|
|
|
<Meta name="keywords" content={t('keywords')} />
|
|
|
|
|
<Meta name="og:type" content="article" />
|
|
|
|
|
<Meta name="og:title" content={ogTitle} />
|
|
|
|
|
<Meta name="og:image" content={ogImage} />
|
|
|
|
|
<Meta name="twitter:image" content={ogImage} />
|
|
|
|
|
<Meta name="og:description" content={description} />
|
|
|
|
|
<Meta name="twitter:card" content="summary_large_image" />
|
|
|
|
|
<Meta name="twitter:title" content={ogTitle} />
|
|
|
|
|
<Meta name="twitter:description" content={description} />
|
|
|
|
|
<Show when={props.isLoaded} fallback={<Loading />}>
|
2023-03-10 17:42:48 +00:00
|
|
|
|
<div class="offset-md-5">
|
2023-12-27 22:56:08 +00:00
|
|
|
|
<div class="row">
|
|
|
|
|
<div class="col-lg-20 col-xl-18">
|
|
|
|
|
<h1>{t('Authors')}</h1>
|
|
|
|
|
<p>{t('Subscribe who you like to tune your personal feed')}</p>
|
|
|
|
|
<Show when={isStatsLoaded()}>
|
2023-12-26 20:33:45 +00:00
|
|
|
|
<ul class={clsx(styles.viewSwitcher, 'view-switcher')}>
|
|
|
|
|
<li
|
|
|
|
|
classList={{
|
|
|
|
|
'view-switcher__item--selected': !searchParams().by || searchParams().by === 'shouts',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<a href="/authors?by=shouts">{t('By shouts')}</a>
|
|
|
|
|
</li>
|
|
|
|
|
<li classList={{ 'view-switcher__item--selected': searchParams().by === 'followers' }}>
|
|
|
|
|
<a href="/authors?by=followers">{t('By popularity')}</a>
|
|
|
|
|
</li>
|
|
|
|
|
<li classList={{ 'view-switcher__item--selected': searchParams().by === 'name' }}>
|
|
|
|
|
<a href="/authors?by=name">{t('By name')}</a>
|
2023-12-13 10:39:31 +00:00
|
|
|
|
</li>
|
2023-12-26 20:33:45 +00:00
|
|
|
|
<Show when={searchParams().by !== 'name'}>
|
|
|
|
|
<li class="view-switcher__search">
|
|
|
|
|
<SearchField onChange={(value) => setSearchQuery(value)} />
|
|
|
|
|
</li>
|
|
|
|
|
</Show>
|
|
|
|
|
</ul>
|
2023-12-27 22:56:08 +00:00
|
|
|
|
</Show>
|
2022-11-19 08:09:52 +00:00
|
|
|
|
</div>
|
2023-12-27 22:56:08 +00:00
|
|
|
|
</div>
|
2023-12-13 10:39:31 +00:00
|
|
|
|
|
|
|
|
|
<Show when={sortedAuthors().length > 0}>
|
|
|
|
|
<Show when={searchParams().by === 'name'}>
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div class="col-lg-20 col-xl-18">
|
|
|
|
|
<ul class={clsx('nodash', styles.alphabet)}>
|
|
|
|
|
<For each={ALPHABET}>
|
|
|
|
|
{(letter, index) => (
|
|
|
|
|
<li>
|
|
|
|
|
<Show when={letter in byLetter()} fallback={letter}>
|
|
|
|
|
<a
|
|
|
|
|
href={`/authors?by=name#letter-${index()}`}
|
|
|
|
|
onClick={(event) => {
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
scrollHandler(`letter-${index()}`)
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{letter}
|
|
|
|
|
</a>
|
|
|
|
|
</Show>
|
|
|
|
|
</li>
|
|
|
|
|
)}
|
|
|
|
|
</For>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2022-11-19 08:09:52 +00:00
|
|
|
|
|
2023-12-13 10:39:31 +00:00
|
|
|
|
<For each={sortedKeys()}>
|
|
|
|
|
{(letter) => (
|
|
|
|
|
<div class={clsx(styles.group, 'group')}>
|
|
|
|
|
<h2 id={`letter-${ALPHABET.indexOf(letter)}`}>{letter}</h2>
|
|
|
|
|
<div class="container">
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div class="col-lg-20">
|
|
|
|
|
<div class="row">
|
|
|
|
|
<For each={byLetter()[letter]}>
|
|
|
|
|
{(author) => (
|
|
|
|
|
<div class={clsx(styles.topic, 'topic col-sm-12 col-md-8')}>
|
|
|
|
|
<div class="topic-title">
|
2023-12-28 00:30:09 +00:00
|
|
|
|
<a href={`/author/${author.slug}`}>{translate(author)}</a>
|
2023-12-24 17:29:16 +00:00
|
|
|
|
<Show when={author.stat}>
|
|
|
|
|
<span class={styles.articlesCounter}>{author.stat.shouts}</span>
|
|
|
|
|
</Show>
|
2023-12-13 10:39:31 +00:00
|
|
|
|
</div>
|
2022-09-09 11:53:35 +00:00
|
|
|
|
</div>
|
2023-12-13 10:39:31 +00:00
|
|
|
|
)}
|
|
|
|
|
</For>
|
|
|
|
|
</div>
|
2022-09-09 11:53:35 +00:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2022-11-11 10:22:07 +00:00
|
|
|
|
</div>
|
2022-11-14 21:58:33 +00:00
|
|
|
|
</div>
|
2023-12-13 10:39:31 +00:00
|
|
|
|
)}
|
|
|
|
|
</For>
|
|
|
|
|
</Show>
|
|
|
|
|
|
|
|
|
|
<Show when={searchParams().by && searchParams().by !== 'name'}>
|
2023-12-26 20:33:45 +00:00
|
|
|
|
<For each={filteredAuthors().slice(0, PAGE_SIZE)}>
|
2023-12-13 10:39:31 +00:00
|
|
|
|
{(author) => (
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div class="col-lg-20 col-xl-18">
|
2024-02-05 12:34:47 +00:00
|
|
|
|
<AuthorBadge
|
|
|
|
|
author={author as Author}
|
|
|
|
|
isFollowed={{
|
|
|
|
|
loaded: Boolean(filteredAuthors()),
|
|
|
|
|
value: isOwnerSubscribed(author.id),
|
|
|
|
|
}}
|
|
|
|
|
/>
|
2023-12-13 10:39:31 +00:00
|
|
|
|
</div>
|
2022-11-30 21:50:33 +00:00
|
|
|
|
</div>
|
2023-12-13 10:39:31 +00:00
|
|
|
|
)}
|
|
|
|
|
</For>
|
|
|
|
|
</Show>
|
|
|
|
|
|
2023-12-26 20:33:45 +00:00
|
|
|
|
<Show when={filteredAuthors().length > PAGE_SIZE + offset() && searchParams().by !== 'name'}>
|
2023-12-13 10:39:31 +00:00
|
|
|
|
<div class="row">
|
|
|
|
|
<div class={clsx(styles.loadMoreContainer, 'col-24 col-md-20')}>
|
|
|
|
|
<button class={clsx('button', styles.loadMoreButton)} onClick={showMore}>
|
|
|
|
|
{t('Load more')}
|
|
|
|
|
</button>
|
2022-11-30 21:50:33 +00:00
|
|
|
|
</div>
|
2022-11-19 08:09:52 +00:00
|
|
|
|
</div>
|
2023-12-13 10:39:31 +00:00
|
|
|
|
</Show>
|
2022-11-20 21:23:12 +00:00
|
|
|
|
</Show>
|
2022-09-09 11:53:35 +00:00
|
|
|
|
</div>
|
|
|
|
|
</Show>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|