all-topics-all-authors-cosmetics-and-refactoring
This commit is contained in:
parent
8dd394eee1
commit
3624004e03
|
@ -9,6 +9,7 @@ import { locale } from '../../stores/ui'
|
|||
import { follow, unfollow } from '../../stores/zine/common'
|
||||
import { clsx } from 'clsx'
|
||||
import { useSession } from '../../context/session'
|
||||
import { StatMetrics } from '../_shared/StatMetrics'
|
||||
|
||||
interface AuthorCardProps {
|
||||
caption?: string
|
||||
|
@ -69,9 +70,11 @@ export const AuthorCard = (props: AuthorCardProps) => {
|
|||
|
||||
<Show when={!props.hideDescription}>
|
||||
{props.isAuthorsList}
|
||||
<div class={styles.authorAbout} classList={{ 'text-truncate': props.truncateBio }}>
|
||||
{bio()}
|
||||
</div>
|
||||
<div
|
||||
class={styles.authorAbout}
|
||||
classList={{ 'text-truncate': props.truncateBio }}
|
||||
innerHTML={props.caption || bio()}
|
||||
></div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { capitalize, plural } from '../../utils'
|
||||
import { capitalize } from '../../utils'
|
||||
import styles from './Card.module.scss'
|
||||
import { createMemo, Show } from 'solid-js'
|
||||
import type { Topic } from '../../graphql/types.gen'
|
||||
import { FollowingEntity } from '../../graphql/types.gen'
|
||||
import { t } from '../../utils/intl'
|
||||
import { locale } from '../../stores/ui'
|
||||
import { follow, unfollow } from '../../stores/zine/common'
|
||||
import { getLogger } from '../../utils/logger'
|
||||
import { clsx } from 'clsx'
|
||||
import { useSession } from '../../context/session'
|
||||
import { StatMetrics } from '../_shared/StatMetrics'
|
||||
|
||||
const log = getLogger('TopicCard')
|
||||
|
||||
|
@ -74,54 +74,6 @@ export const TopicCard = (props: TopicProps) => {
|
|||
{props.topic.body}
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={props.topic?.stat}>
|
||||
<div class={styles.topicDetails}>
|
||||
<Show when={props.showPublications}>
|
||||
<span class={styles.topicDetailsItem} classList={{ compact: props.compact }}>
|
||||
{props.topic.stat?.shouts +
|
||||
' ' +
|
||||
t('post') +
|
||||
plural(
|
||||
props.topic.stat?.shouts || 0,
|
||||
locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']
|
||||
)}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={!props.compact}>
|
||||
<span class={styles.topicDetailsItem} classList={{ compact: props.compact }}>
|
||||
{props.topic.stat?.authors +
|
||||
' ' +
|
||||
t('author') +
|
||||
plural(
|
||||
props.topic.stat?.authors || 0,
|
||||
locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']
|
||||
)}
|
||||
</span>
|
||||
<span class={styles.topicDetailsItem} classList={{ compact: props.compact }}>
|
||||
{props.topic.stat?.followers +
|
||||
' ' +
|
||||
t('follower') +
|
||||
plural(
|
||||
props.topic.stat?.followers || 0,
|
||||
locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']
|
||||
)}
|
||||
</span>
|
||||
{/*FIXME*/}
|
||||
{/*<Show when={false && !props.subscribeButtonBottom}>*/}
|
||||
{/* <span class='topic-details__item'>*/}
|
||||
{/* {topic().stat?.viewed +*/}
|
||||
{/* ' ' +*/}
|
||||
{/* t('view') +*/}
|
||||
{/* plural(*/}
|
||||
{/* topic().stat?.viewed || 0,*/}
|
||||
{/* locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']*/}
|
||||
{/* )}*/}
|
||||
{/* </span>*/}
|
||||
{/*</Show>*/}
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
<div
|
||||
class={styles.controlContainer}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { createEffect, createMemo, createSignal, For, Show } from 'solid-js'
|
||||
import { createEffect, createMemo, createSignal, For, onMount, Show } from 'solid-js'
|
||||
import type { Author } from '../../graphql/types.gen'
|
||||
import { AuthorCard } from '../Author/Card'
|
||||
import { t } from '../../utils/intl'
|
||||
|
@ -11,9 +11,10 @@ import { locale } from '../../stores/ui'
|
|||
import { translit } from '../../utils/ru2en'
|
||||
import { SearchField } from '../_shared/SearchField'
|
||||
import { scrollHandler } from '../../utils/scroll'
|
||||
import { StatMetrics } from '../_shared/StatMetrics'
|
||||
|
||||
type AllAuthorsPageSearchParams = {
|
||||
by: '' | 'name' | 'shouts' | 'rating'
|
||||
by: '' | 'name' | 'shouts' | 'followers'
|
||||
}
|
||||
|
||||
type Props = {
|
||||
|
@ -24,23 +25,27 @@ const PAGE_SIZE = 20
|
|||
const ALPHABET = [...'@АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ']
|
||||
|
||||
export const AllAuthorsView = (props: Props) => {
|
||||
const { sortedAuthors } = useAuthorsStore({ authors: props.authors })
|
||||
const [limit, setLimit] = createSignal(PAGE_SIZE)
|
||||
const { searchParams, changeSearchParam } = useRouter<AllAuthorsPageSearchParams>()
|
||||
const { sortedAuthors } = useAuthorsStore({
|
||||
authors: props.authors,
|
||||
sortBy: searchParams().by || 'shouts'
|
||||
})
|
||||
|
||||
const { session } = useSession()
|
||||
|
||||
onMount(() => changeSearchParam('by', 'shouts'))
|
||||
createEffect(() => {
|
||||
setAuthorsSort(searchParams().by || 'shouts')
|
||||
setLimit(PAGE_SIZE)
|
||||
})
|
||||
|
||||
const subscribed = (s) => Boolean(session()?.news?.authors && session()?.news?.authors?.includes(s || ''))
|
||||
|
||||
const { searchParams, changeSearchParam } = useRouter<AllAuthorsPageSearchParams>()
|
||||
|
||||
const byLetter = createMemo<{ [letter: string]: Author[] }>(() => {
|
||||
return sortedAuthors().reduce((acc, author) => {
|
||||
let letter = author.name.trim().split(' ').pop().at(0).toUpperCase()
|
||||
if (!/[А-Я]/i.test(letter) && locale() === 'ru') letter = '@'
|
||||
if (!/[А-я]/i.test(letter) && locale() === 'ru') letter = '@'
|
||||
if (!acc[letter]) acc[letter] = []
|
||||
acc[letter].push(author)
|
||||
return acc
|
||||
|
@ -64,8 +69,8 @@ export const AllAuthorsView = (props: Props) => {
|
|||
<li classList={{ selected: searchParams().by === 'shouts' }}>
|
||||
<a href="/authors?by=shouts">{t('By shouts')}</a>
|
||||
</li>
|
||||
<li classList={{ selected: searchParams().by === 'rating' }}>
|
||||
<a href="/authors?by=rating">{t('By rating')}</a>
|
||||
<li classList={{ selected: searchParams().by === 'followers' }}>
|
||||
<a href="/authors?by=followers">{t('By rating')}</a>
|
||||
</li>
|
||||
<li classList={{ selected: !searchParams().by || searchParams().by === 'name' }}>
|
||||
<a href="/authors?by=name">{t('By name')}</a>
|
||||
|
@ -167,15 +172,18 @@ export const AllAuthorsView = (props: Props) => {
|
|||
<Show when={searchResults().length > 0}>
|
||||
<For each={searchResults().slice(0, limit())}>
|
||||
{(author) => (
|
||||
<AuthorCard
|
||||
author={author}
|
||||
compact={false}
|
||||
hasLink={true}
|
||||
subscribed={subscribed(author.slug)}
|
||||
noSocialButtons={true}
|
||||
isAuthorsList={true}
|
||||
truncateBio={true}
|
||||
/>
|
||||
<>
|
||||
<AuthorCard
|
||||
author={author}
|
||||
compact={false}
|
||||
hasLink={true}
|
||||
subscribed={subscribed(author.slug)}
|
||||
noSocialButtons={true}
|
||||
isAuthorsList={true}
|
||||
truncateBio={true}
|
||||
/>
|
||||
<StatMetrics fields={['shouts', 'followers', 'comments']} stat={author.stat} />
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
|
@ -185,15 +193,18 @@ export const AllAuthorsView = (props: Props) => {
|
|||
<div class="col-lg-10 col-xl-9">
|
||||
<For each={sortedAuthors().slice(0, limit())}>
|
||||
{(author) => (
|
||||
<AuthorCard
|
||||
author={author}
|
||||
compact={false}
|
||||
hasLink={true}
|
||||
subscribed={subscribed(author.slug)}
|
||||
noSocialButtons={true}
|
||||
isAuthorsList={true}
|
||||
truncateBio={true}
|
||||
/>
|
||||
<>
|
||||
<AuthorCard
|
||||
author={author}
|
||||
compact={false}
|
||||
hasLink={true}
|
||||
subscribed={subscribed(author.slug)}
|
||||
noSocialButtons={true}
|
||||
isAuthorsList={true}
|
||||
truncateBio={true}
|
||||
/>
|
||||
<StatMetrics fields={['shouts', 'followers', 'comments']} stat={author.stat} />
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { createEffect, createMemo, createSignal, For, Show } from 'solid-js'
|
||||
import { createEffect, createMemo, createSignal, For, onMount, Show } from 'solid-js'
|
||||
import type { Topic } from '../../graphql/types.gen'
|
||||
import { t } from '../../utils/intl'
|
||||
import { setTopicsSort, useTopicsStore } from '../../stores/zine/topics'
|
||||
|
@ -11,6 +11,7 @@ import { translit } from '../../utils/ru2en'
|
|||
import styles from '../../styles/AllTopics.module.scss'
|
||||
import { SearchField } from '../_shared/SearchField'
|
||||
import { scrollHandler } from '../../utils/scroll'
|
||||
import { StatMetrics } from '../_shared/StatMetrics'
|
||||
|
||||
type AllTopicsPageSearchParams = {
|
||||
by: 'shouts' | 'authors' | 'title' | ''
|
||||
|
@ -34,6 +35,7 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
|
|||
|
||||
const { session } = useSession()
|
||||
|
||||
onMount(() => changeSearchParam('by', 'shouts'))
|
||||
createEffect(() => {
|
||||
setTopicsSort(searchParams().by || 'shouts')
|
||||
setLimit(PAGE_SIZE)
|
||||
|
@ -182,12 +184,15 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
|
|||
<Show when={searchParams().by && searchParams().by !== 'title'}>
|
||||
<For each={sortedTopics().slice(0, limit())}>
|
||||
{(topic) => (
|
||||
<TopicCard
|
||||
topic={topic}
|
||||
compact={false}
|
||||
subscribed={subscribed(topic.slug)}
|
||||
showPublications={true}
|
||||
/>
|
||||
<>
|
||||
<TopicCard
|
||||
topic={topic}
|
||||
compact={false}
|
||||
subscribed={subscribed(topic.slug)}
|
||||
showPublications={true}
|
||||
/>
|
||||
<StatMetrics fields={['shouts', 'authors', 'followers']} stat={topic.stat} />
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
|
|
34
src/components/_shared/Stat.module.scss
Normal file
34
src/components/_shared/Stat.module.scss
Normal file
|
@ -0,0 +1,34 @@
|
|||
.statMetrics {
|
||||
@include font-size(1.7rem);
|
||||
|
||||
color: #9fa1a7;
|
||||
display: flex;
|
||||
margin-bottom: 1em;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.statMetricsItem {
|
||||
@include font-size(1.5rem);
|
||||
|
||||
margin-right: 1.6rem;
|
||||
white-space: nowrap;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&.compact {
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
&.followers {
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
&.button {
|
||||
float: right;
|
||||
}
|
||||
}
|
35
src/components/_shared/StatMetrics.tsx
Normal file
35
src/components/_shared/StatMetrics.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { For } from 'solid-js'
|
||||
import type { Stat, TopicStat } from '../../graphql/types.gen'
|
||||
import { locale } from '../../stores/ui'
|
||||
import { plural } from '../../utils'
|
||||
import { t } from '../../utils/intl'
|
||||
import styles from './Stat.module.scss'
|
||||
|
||||
interface StatMetricsProps {
|
||||
fields?: string[]
|
||||
stat: Stat | TopicStat
|
||||
compact?: boolean
|
||||
}
|
||||
|
||||
const pseudonames = {
|
||||
comments: 'discussions'
|
||||
}
|
||||
|
||||
const nos = (s) => s.slice(0, s.length - 1)
|
||||
|
||||
export const StatMetrics = (props: StatMetricsProps) => {
|
||||
return (
|
||||
<div class={styles.statMetrics}>
|
||||
<For each={props.fields}>
|
||||
{(entity: string) => (
|
||||
<span class={styles.statMetricsItem} classList={{ compact: props.compact }}>
|
||||
{props.stat[entity] +
|
||||
' ' +
|
||||
t(nos(pseudonames[entity] || entity)) +
|
||||
plural(props.stat[entity] || 0, locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's'])}
|
||||
</span>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -13,6 +13,7 @@ export default gql`
|
|||
stat {
|
||||
shouts
|
||||
followers
|
||||
comments: commented
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,10 +35,10 @@ export type Author = {
|
|||
}
|
||||
|
||||
export type AuthorStat = {
|
||||
rating?: Maybe<Scalars['Int']>
|
||||
commented?: Maybe<Scalars['Int']>
|
||||
followers?: Maybe<Scalars['Int']>
|
||||
followings?: Maybe<Scalars['Int']>
|
||||
rating?: Maybe<Scalars['Int']>
|
||||
shouts?: Maybe<Scalars['Int']>
|
||||
}
|
||||
|
||||
|
@ -615,6 +615,8 @@ export type Stat = {
|
|||
rating?: Maybe<Scalars['Int']>
|
||||
reacted?: Maybe<Scalars['Int']>
|
||||
viewed?: Maybe<Scalars['Int']>
|
||||
shouts?: Maybe<Scalars['Int']>
|
||||
followers?: Maybe<Scalars['Int']>
|
||||
}
|
||||
|
||||
export type Subscription = {
|
||||
|
|
|
@ -178,5 +178,7 @@
|
|||
"topics": "темы",
|
||||
"user already exist": "пользователь уже существует",
|
||||
"view": "просмотр",
|
||||
"zine": "журнал"
|
||||
"zine": "журнал",
|
||||
"shout": "пост",
|
||||
"discussion": "дискурс"
|
||||
}
|
||||
|
|
|
@ -2,8 +2,9 @@ import { apiClient } from '../../utils/apiClient'
|
|||
import type { Author } from '../../graphql/types.gen'
|
||||
import { createSignal } from 'solid-js'
|
||||
import { createLazyMemo } from '@solid-primitives/memo'
|
||||
import { byStat } from '../../utils/sortby'
|
||||
|
||||
export type AuthorsSortBy = 'shouts' | 'name' | 'rating'
|
||||
export type AuthorsSortBy = 'shouts' | 'name' | 'followers'
|
||||
|
||||
const [sortAllBy, setSortAllBy] = createSignal<AuthorsSortBy>('shouts')
|
||||
|
||||
|
@ -15,21 +16,15 @@ const [authorsByTopic, setAuthorsByTopic] = createSignal<{ [topicSlug: string]:
|
|||
const sortedAuthors = createLazyMemo(() => {
|
||||
const authors = Object.values(authorEntities())
|
||||
switch (sortAllBy()) {
|
||||
// case 'created': {
|
||||
// log.debug('sorted by created')
|
||||
// authors.sort(byCreated)
|
||||
// break
|
||||
// }
|
||||
case 'rating': {
|
||||
// TODO:
|
||||
case 'followers': {
|
||||
authors.sort(byStat('followers'))
|
||||
break
|
||||
}
|
||||
case 'shouts': {
|
||||
// TODO:
|
||||
authors.sort(byStat('shouts'))
|
||||
break
|
||||
}
|
||||
case 'name': {
|
||||
console.debug('sorted by name')
|
||||
authors.sort((a, b) => a.name.localeCompare(b.name))
|
||||
break
|
||||
}
|
||||
|
@ -84,9 +79,13 @@ export const loadAllAuthors = async (): Promise<void> => {
|
|||
|
||||
type InitialState = {
|
||||
authors?: Author[]
|
||||
sortBy?: AuthorsSortBy
|
||||
}
|
||||
|
||||
export const useAuthorsStore = (initialState: InitialState = {}) => {
|
||||
if (initialState.sortBy) {
|
||||
setSortAllBy(initialState.sortBy)
|
||||
}
|
||||
addAuthors([...(initialState.authors || [])])
|
||||
|
||||
return { authorEntities, sortedAuthors, authorsByTopic }
|
||||
|
|
|
@ -260,9 +260,7 @@ export const apiClient = {
|
|||
},
|
||||
getReactionsBy: async ({ by, limit = REACTIONS_AMOUNT_PER_PAGE, offset = 0 }) => {
|
||||
const resp = await publicGraphQLClient.query(reactionsLoadBy, { by, limit, offset }).toPromise()
|
||||
|
||||
console.log('resactions response', resp)
|
||||
|
||||
resp.error ?? console.error(resp.error)
|
||||
return resp.data.loadReactionsBy
|
||||
},
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@ export const byLength = (
|
|||
return 0
|
||||
}
|
||||
|
||||
// TODO more typing
|
||||
export const byStat = (metric: keyof Stat) => {
|
||||
return (a, b) => {
|
||||
const x = (a?.stat && a.stat[metric]) || 0
|
||||
|
|
Loading…
Reference in New Issue
Block a user