feed period select (#340)
* feed period select * fix, unused code removed * Fix styles * Fix styles * Fix styles --------- Co-authored-by: Igor Lobanov <igor.lobanov@onetwotrip.com> Co-authored-by: ilya-bkv <i.yablokov@ccmp.me>
This commit is contained in:
parent
88d35ce2bc
commit
3a7d138eef
|
@ -357,9 +357,12 @@
|
|||
"This comment has not yet been rated": "This comment has not yet been rated",
|
||||
"This email is already taken. If it's you": "This email is already taken. If it's you",
|
||||
"This functionality is currently not available, we would like to work on this issue. Use the download link.": "This functionality is currently not available, we would like to work on this issue. Use the download link.",
|
||||
"This month": "This month",
|
||||
"This post has not been rated yet": "This post has not been rated yet",
|
||||
"This way we ll realize that you re a real person and ll take your vote into account. And you ll see how others voted": "This way we ll realize that you re a real person and ll take your vote into account. And you ll see how others voted",
|
||||
"This way you ll be able to subscribe to authors, interesting topics and customize your feed": "This way you ll be able to subscribe to authors, interesting topics and customize your feed",
|
||||
"This week": "This week",
|
||||
"This year": "This year",
|
||||
"To leave a comment please": "To leave a comment please",
|
||||
"To write a comment, you must": "To write a comment, you must",
|
||||
"Top authors": "Authors rating",
|
||||
|
|
|
@ -377,9 +377,12 @@
|
|||
"This comment has not yet been rated": "Этот комментарий еще пока никто не оценил",
|
||||
"This email is already taken. If it's you": "Такой email уже зарегистрирован. Если это вы",
|
||||
"This functionality is currently not available, we would like to work on this issue. Use the download link.": "В данный момент этот функционал не доступен, бы работаем над этой проблемой. Воспользуйтесь загрузкой по ссылке.",
|
||||
"This month": "За месяц",
|
||||
"This post has not been rated yet": "Эту публикацию еще пока никто не оценил",
|
||||
"This way we ll realize that you re a real person and ll take your vote into account. And you ll see how others voted": "Так мы поймем, что вы реальный человек, и учтем ваш голос. А вы увидите, как проголосовали другие",
|
||||
"This way you ll be able to subscribe to authors, interesting topics and customize your feed": "Так вы сможете подписаться на авторов, интересные темы и настроить свою ленту",
|
||||
"This week": "За неделю",
|
||||
"This year": "За год",
|
||||
"To leave a comment please": "Чтобы оставить комментарий, необходимо",
|
||||
"To write a comment, you must": "Чтобы написать комментарий, необходимо",
|
||||
"Top authors": "Рейтинг авторов",
|
||||
|
|
|
@ -135,7 +135,7 @@ export const FullArticle = (props: Props) => {
|
|||
scrollTo(commentsRef.current)
|
||||
}
|
||||
|
||||
const { searchParams, changeSearchParam } = useRouter<ArticlePageSearchParams>()
|
||||
const { searchParams, changeSearchParams } = useRouter<ArticlePageSearchParams>()
|
||||
|
||||
createEffect(() => {
|
||||
if (props.scrollToComments) {
|
||||
|
@ -146,7 +146,7 @@ export const FullArticle = (props: Props) => {
|
|||
createEffect(() => {
|
||||
if (searchParams()?.scrollTo === 'comments' && commentsRef.current) {
|
||||
scrollToComments()
|
||||
changeSearchParam({
|
||||
changeSearchParams({
|
||||
scrollTo: null,
|
||||
})
|
||||
}
|
||||
|
@ -158,7 +158,7 @@ export const FullArticle = (props: Props) => {
|
|||
`[id='comment_${searchParams().commentId}']`,
|
||||
)
|
||||
|
||||
changeSearchParam({ commentId: null })
|
||||
changeSearchParams({ commentId: null })
|
||||
|
||||
if (commentElement) {
|
||||
scrollTo(commentElement)
|
||||
|
|
|
@ -13,7 +13,7 @@ type Props = {
|
|||
|
||||
export const AuthGuard = (props: Props) => {
|
||||
const { isAuthenticated, isSessionLoaded } = useSession()
|
||||
const { changeSearchParam } = useRouter<RootSearchParams & AuthModalSearchParams>()
|
||||
const { changeSearchParams } = useRouter<RootSearchParams & AuthModalSearchParams>()
|
||||
|
||||
createEffect(() => {
|
||||
if (props.disabled) {
|
||||
|
@ -23,7 +23,7 @@ export const AuthGuard = (props: Props) => {
|
|||
if (isAuthenticated()) {
|
||||
hideModal()
|
||||
} else {
|
||||
changeSearchParam(
|
||||
changeSearchParams(
|
||||
{
|
||||
source: 'authguard',
|
||||
modal: 'auth',
|
||||
|
|
|
@ -29,7 +29,7 @@ export const AuthorBadge = (props: Props) => {
|
|||
subscriptions,
|
||||
actions: { loadSubscriptions, requireAuthentication },
|
||||
} = useSession()
|
||||
const { changeSearchParam } = useRouter()
|
||||
const { changeSearchParams } = useRouter()
|
||||
const { t, formatDate } = useLocalize()
|
||||
const subscribed = createMemo(() =>
|
||||
subscriptions().authors.some((author) => author.slug === props.author.slug),
|
||||
|
@ -54,7 +54,7 @@ export const AuthorBadge = (props: Props) => {
|
|||
const initChat = () => {
|
||||
requireAuthentication(() => {
|
||||
openPage(router, `inbox`)
|
||||
changeSearchParam({
|
||||
changeSearchParams({
|
||||
initChat: props.author.id.toString(),
|
||||
})
|
||||
}, 'discussions')
|
||||
|
|
|
@ -72,11 +72,11 @@ export const AuthorCard = (props: Props) => {
|
|||
})
|
||||
|
||||
// TODO: reimplement AuthorCard
|
||||
const { changeSearchParam } = useRouter()
|
||||
const { changeSearchParams } = useRouter()
|
||||
const initChat = () => {
|
||||
requireAuthentication(() => {
|
||||
openPage(router, `inbox`)
|
||||
changeSearchParam({
|
||||
changeSearchParams({
|
||||
initChat: props.author.id.toString(),
|
||||
})
|
||||
}, 'discussions')
|
||||
|
|
|
@ -7,7 +7,7 @@ import styles from './Hero.module.scss'
|
|||
|
||||
export default () => {
|
||||
const { t } = useLocalize()
|
||||
const { changeSearchParam } = useRouter<AuthModalSearchParams>()
|
||||
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
|
||||
|
||||
return (
|
||||
<div class={styles.aboutDiscours}>
|
||||
|
@ -28,7 +28,7 @@ export default () => {
|
|||
class="button"
|
||||
onClick={() => {
|
||||
showModal('auth')
|
||||
changeSearchParam({
|
||||
changeSearchParams({
|
||||
mode: 'register',
|
||||
})
|
||||
}}
|
||||
|
|
|
@ -98,11 +98,11 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
|||
|
||||
const canEdit = () => props.article.authors?.some((a) => a.slug === user()?.slug)
|
||||
|
||||
const { changeSearchParam } = useRouter()
|
||||
const { changeSearchParams } = useRouter()
|
||||
const scrollToComments = (event) => {
|
||||
event.preventDefault()
|
||||
openPage(router, 'article', { slug: props.article.slug })
|
||||
changeSearchParam({
|
||||
changeSearchParams({
|
||||
scrollTo: 'comments',
|
||||
})
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ type FormFields = {
|
|||
type ValidationErrors = Partial<Record<keyof FormFields, string | JSX.Element>>
|
||||
|
||||
export const ForgotPasswordForm = () => {
|
||||
const { changeSearchParam } = useRouter<AuthModalSearchParams>()
|
||||
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
|
||||
const { t, lang } = useLocalize()
|
||||
const handleEmailInput = (newEmail: string) => {
|
||||
setValidationErrors(({ email: _notNeeded, ...rest }) => rest)
|
||||
|
@ -119,7 +119,7 @@ export const ForgotPasswordForm = () => {
|
|||
href="#"
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
changeSearchParam({
|
||||
changeSearchParams({
|
||||
mode: 'register',
|
||||
})
|
||||
}}
|
||||
|
@ -141,7 +141,7 @@ export const ForgotPasswordForm = () => {
|
|||
<span
|
||||
class={styles.authLink}
|
||||
onClick={() =>
|
||||
changeSearchParam({
|
||||
changeSearchParams({
|
||||
mode: 'login',
|
||||
})
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ export const LoginForm = () => {
|
|||
actions: { signIn },
|
||||
} = useSession()
|
||||
|
||||
const { changeSearchParam } = useRouter<AuthModalSearchParams>()
|
||||
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
|
||||
|
||||
const [password, setPassword] = createSignal('')
|
||||
|
||||
|
@ -201,7 +201,7 @@ export const LoginForm = () => {
|
|||
<span
|
||||
class="link"
|
||||
onClick={() =>
|
||||
changeSearchParam({
|
||||
changeSearchParams({
|
||||
mode: 'forgot-password',
|
||||
})
|
||||
}
|
||||
|
@ -218,7 +218,7 @@ export const LoginForm = () => {
|
|||
<span
|
||||
class={styles.authLink}
|
||||
onClick={() =>
|
||||
changeSearchParam({
|
||||
changeSearchParams({
|
||||
mode: 'register',
|
||||
})
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ const handleEmailInput = (newEmail: string) => {
|
|||
}
|
||||
|
||||
export const RegisterForm = () => {
|
||||
const { changeSearchParam } = useRouter<AuthModalSearchParams>()
|
||||
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
|
||||
const { t } = useLocalize()
|
||||
const { emailChecks } = useEmailChecks()
|
||||
|
||||
|
@ -202,7 +202,7 @@ export const RegisterForm = () => {
|
|||
href="#"
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
changeSearchParam({
|
||||
changeSearchParams({
|
||||
mode: 'login',
|
||||
})
|
||||
}}
|
||||
|
@ -255,7 +255,7 @@ export const RegisterForm = () => {
|
|||
<span
|
||||
class={styles.authLink}
|
||||
onClick={() =>
|
||||
changeSearchParam({
|
||||
changeSearchParams({
|
||||
mode: 'login',
|
||||
})
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ export const NotificationView = (props: Props) => {
|
|||
actions: { markNotificationAsRead, hideNotificationsPanel },
|
||||
} = useNotifications()
|
||||
|
||||
const { changeSearchParam } = useRouter<ArticlePageSearchParams>()
|
||||
const { changeSearchParams } = useRouter<ArticlePageSearchParams>()
|
||||
|
||||
const { t, formatDate, formatTime } = useLocalize()
|
||||
|
||||
|
@ -139,7 +139,7 @@ export const NotificationView = (props: Props) => {
|
|||
openPage(router, 'article', { slug: data().shout.slug })
|
||||
|
||||
if (data().reactionIds) {
|
||||
changeSearchParam({ commentId: data().reactionIds[0].toString() })
|
||||
changeSearchParams({ commentId: data().reactionIds[0].toString() })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ const ALPHABET = [...'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫ
|
|||
export const AllAuthorsView = (props: Props) => {
|
||||
const { t, lang } = useLocalize()
|
||||
const [limit, setLimit] = createSignal(PAGE_SIZE)
|
||||
const { searchParams, changeSearchParam } = useRouter<AllAuthorsPageSearchParams>()
|
||||
const { searchParams, changeSearchParams } = useRouter<AllAuthorsPageSearchParams>()
|
||||
const { sortedAuthors } = useAuthorsStore({
|
||||
authors: props.authors,
|
||||
sortBy: searchParams().by || 'shouts',
|
||||
|
@ -41,7 +41,7 @@ export const AllAuthorsView = (props: Props) => {
|
|||
|
||||
createEffect(() => {
|
||||
if (!searchParams().by) {
|
||||
changeSearchParam({
|
||||
changeSearchParams({
|
||||
by: 'shouts',
|
||||
})
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ const ALPHABET = [...'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫ
|
|||
|
||||
export const AllTopicsView = (props: Props) => {
|
||||
const { t, lang } = useLocalize()
|
||||
const { searchParams, changeSearchParam } = useRouter<AllTopicsPageSearchParams>()
|
||||
const { searchParams, changeSearchParams } = useRouter<AllTopicsPageSearchParams>()
|
||||
const [limit, setLimit] = createSignal(PAGE_SIZE)
|
||||
|
||||
const { sortedTopics } = useTopicsStore({
|
||||
|
@ -43,7 +43,7 @@ export const AllTopicsView = (props: Props) => {
|
|||
|
||||
createEffect(() => {
|
||||
if (!searchParams().by) {
|
||||
changeSearchParam({
|
||||
changeSearchParams({
|
||||
by: 'shouts',
|
||||
})
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ export const Expo = (props: Props) => {
|
|||
})
|
||||
|
||||
const getLoadShoutsFilters = (additionalFilters: LoadShoutsFilters = {}): LoadShoutsFilters => {
|
||||
const filters = { ...additionalFilters }
|
||||
const filters = { visibility: 'public', ...additionalFilters }
|
||||
|
||||
if (props.layout) {
|
||||
filters.layout = props.layout
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
.feedFilter {
|
||||
margin-bottom: 4.8rem;
|
||||
margin-top: 0.2em;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
margin-right: 4rem !important;
|
||||
}
|
||||
|
@ -192,3 +189,25 @@
|
|||
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.filtersContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 4rem;
|
||||
|
||||
.feedFilter {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
& > li {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.periodSwitcher {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { Author, LoadShoutsOptions, Reaction, Shout } from '../../../graphq
|
|||
import { getPagePath } from '@nanostores/router'
|
||||
import { Meta } from '@solidjs/meta'
|
||||
import { clsx } from 'clsx'
|
||||
import { createEffect, createSignal, For, on, onMount, Show } from 'solid-js'
|
||||
import { createEffect, createMemo, createSignal, For, on, onMount, Show } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { useReactions } from '../../../context/reactions'
|
||||
|
@ -13,6 +13,8 @@ import { useTopAuthorsStore } from '../../../stores/zine/topAuthors'
|
|||
import { useTopicsStore } from '../../../stores/zine/topics'
|
||||
import { apiClient } from '../../../utils/apiClient'
|
||||
import { getImageUrl } from '../../../utils/getImageUrl'
|
||||
import { getServerDate } from '../../../utils/getServerDate'
|
||||
import { DropDown } from '../../_shared/DropDown'
|
||||
import { Icon } from '../../_shared/Icon'
|
||||
import { Loading } from '../../_shared/Loading'
|
||||
import { CommentDate } from '../../Article/CommentDate'
|
||||
|
@ -28,8 +30,16 @@ import stylesTopic from '../../Feed/CardTopic.module.scss'
|
|||
export const FEED_PAGE_SIZE = 20
|
||||
const UNRATED_ARTICLES_COUNT = 5
|
||||
|
||||
type FeedPeriod = 'week' | 'month' | 'year'
|
||||
|
||||
type PeriodItem = {
|
||||
value: FeedPeriod
|
||||
title: string
|
||||
}
|
||||
|
||||
type FeedSearchParams = {
|
||||
by: 'publish_date' | 'rating' | 'last_comment'
|
||||
period: FeedPeriod
|
||||
}
|
||||
|
||||
const getOrderBy = (by: FeedSearchParams['by']) => {
|
||||
|
@ -53,7 +63,16 @@ type Props = {
|
|||
|
||||
export const Feed = (props: Props) => {
|
||||
const { t } = useLocalize()
|
||||
const { page, searchParams } = useRouter<FeedSearchParams>()
|
||||
|
||||
const monthPeriod: PeriodItem = { value: 'month', title: t('This month') }
|
||||
|
||||
const periods: PeriodItem[] = [
|
||||
{ value: 'week', title: t('This week') },
|
||||
monthPeriod,
|
||||
{ value: 'year', title: t('This year') },
|
||||
]
|
||||
|
||||
const { page, searchParams, changeSearchParams } = useRouter<FeedSearchParams>()
|
||||
const [isLoading, setIsLoading] = createSignal(false)
|
||||
const [isRightColumnLoaded, setIsRightColumnLoaded] = createSignal(false)
|
||||
|
||||
|
@ -64,6 +83,16 @@ export const Feed = (props: Props) => {
|
|||
const [topComments, setTopComments] = createSignal<Reaction[]>([])
|
||||
const [unratedArticles, setUnratedArticles] = createSignal<Shout[]>([])
|
||||
|
||||
const currentPeriod = createMemo(() => {
|
||||
const period = periods.find((p) => p.value === searchParams().period)
|
||||
|
||||
if (!period) {
|
||||
return monthPeriod
|
||||
}
|
||||
|
||||
return period
|
||||
})
|
||||
|
||||
const {
|
||||
actions: { loadReactionsBy },
|
||||
} = useReactions()
|
||||
|
@ -86,7 +115,7 @@ export const Feed = (props: Props) => {
|
|||
|
||||
createEffect(
|
||||
on(
|
||||
() => page().route + searchParams().by,
|
||||
() => page().route + searchParams().by + searchParams().period,
|
||||
() => {
|
||||
resetSortedArticles()
|
||||
loadMore()
|
||||
|
@ -94,6 +123,21 @@ export const Feed = (props: Props) => {
|
|||
{ defer: true },
|
||||
),
|
||||
)
|
||||
|
||||
const getFromDate = (period: FeedPeriod): Date => {
|
||||
const now = new Date()
|
||||
switch (period) {
|
||||
case 'week': {
|
||||
return new Date(now.setDate(now.getDate() - 7))
|
||||
}
|
||||
case 'month': {
|
||||
return new Date(now.setMonth(now.getMonth() - 1))
|
||||
}
|
||||
case 'year': {
|
||||
return new Date(now.setFullYear(now.getFullYear() - 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
const loadFeedShouts = () => {
|
||||
const options: LoadShoutsOptions = {
|
||||
limit: FEED_PAGE_SIZE,
|
||||
|
@ -106,6 +150,12 @@ export const Feed = (props: Props) => {
|
|||
options.order_by = orderBy
|
||||
}
|
||||
|
||||
if (searchParams().by && searchParams().by !== 'publish_date') {
|
||||
const period = searchParams().period || 'month'
|
||||
const fromDate = getFromDate(period)
|
||||
options.filters = { fromDate: getServerDate(fromDate) }
|
||||
}
|
||||
|
||||
return props.loadShouts(options)
|
||||
}
|
||||
|
||||
|
@ -148,32 +198,49 @@ export const Feed = (props: Props) => {
|
|||
</div>
|
||||
|
||||
<div class="col-md-12 offset-xl-1">
|
||||
<ul class={clsx(styles.feedFilter, 'view-switcher')}>
|
||||
<li
|
||||
class={clsx({
|
||||
'view-switcher__item--selected': searchParams().by === 'publish_date' || !searchParams().by,
|
||||
})}
|
||||
>
|
||||
<a href={getPagePath(router, page().route)}>{t('Recent')}</a>
|
||||
</li>
|
||||
{/*<li>*/}
|
||||
{/* <a href="/feed/?by=views">{t('Most read')}</a>*/}
|
||||
{/*</li>*/}
|
||||
<li
|
||||
class={clsx({
|
||||
'view-switcher__item--selected': searchParams().by === 'rating',
|
||||
})}
|
||||
>
|
||||
<a href={`${getPagePath(router, page().route)}?by=rating`}>{t('Top rated')}</a>
|
||||
</li>
|
||||
<li
|
||||
class={clsx({
|
||||
'view-switcher__item--selected': searchParams().by === 'last_comment',
|
||||
})}
|
||||
>
|
||||
<a href={`${getPagePath(router, page().route)}?by=last_comment`}>{t('Most commented')}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class={styles.filtersContainer}>
|
||||
<ul class={clsx('view-switcher', styles.feedFilter)}>
|
||||
<li
|
||||
class={clsx({
|
||||
'view-switcher__item--selected':
|
||||
searchParams().by === 'publish_date' || !searchParams().by,
|
||||
})}
|
||||
>
|
||||
<a href={getPagePath(router, page().route)}>{t('Recent')}</a>
|
||||
</li>
|
||||
{/*<li>*/}
|
||||
{/* <a href="/feed/?by=views">{t('Most read')}</a>*/}
|
||||
{/*</li>*/}
|
||||
<li
|
||||
class={clsx({
|
||||
'view-switcher__item--selected': searchParams().by === 'rating',
|
||||
})}
|
||||
>
|
||||
<span class="link" onClick={() => changeSearchParams({ by: 'rating' })}>
|
||||
{t('Top rated')}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
class={clsx({
|
||||
'view-switcher__item--selected': searchParams().by === 'last_comment',
|
||||
})}
|
||||
>
|
||||
<span class="link" onClick={() => changeSearchParams({ by: 'last_comment' })}>
|
||||
{t('Most commented')}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<Show when={searchParams().by && searchParams().by !== 'publish_date'}>
|
||||
<div>
|
||||
<DropDown
|
||||
options={periods}
|
||||
currentOption={currentPeriod()}
|
||||
triggerCssClass={styles.periodSwitcher}
|
||||
onChange={(period) => changeSearchParams({ period: period.value })}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<Show when={!isLoading()} fallback={<Loading />}>
|
||||
<Show when={sortedArticles().length > 0}>
|
||||
|
|
|
@ -115,9 +115,7 @@ export const HomeView = (props: Props) => {
|
|||
wrapper={'top-article'}
|
||||
nodate={true}
|
||||
/>
|
||||
|
||||
<Row3 articles={sortedArticles().slice(6, 9)} nodate={true} />
|
||||
|
||||
<Beside
|
||||
beside={sortedArticles()[9]}
|
||||
title={t('Top authors')}
|
||||
|
@ -125,15 +123,11 @@ export const HomeView = (props: Props) => {
|
|||
wrapper={'author'}
|
||||
nodate={true}
|
||||
/>
|
||||
|
||||
<Show when={topMonthArticles()}>
|
||||
<ArticleCardSwiper title={t('Top month articles')} slides={topMonthArticles()} />
|
||||
</Show>
|
||||
|
||||
<Row2 articles={sortedArticles().slice(10, 12)} nodate={true} />
|
||||
|
||||
<RowShort articles={sortedArticles().slice(12, 16)} />
|
||||
|
||||
<Row1 article={sortedArticles()[16]} nodate={true} />
|
||||
<Row3 articles={sortedArticles().slice(17, 20)} nodate={true} />
|
||||
<Row3
|
||||
|
@ -141,13 +135,10 @@ export const HomeView = (props: Props) => {
|
|||
header={<h2>{t('Top commented')}</h2>}
|
||||
nodate={true}
|
||||
/>
|
||||
|
||||
{randomLayout()}
|
||||
|
||||
<Show when={topArticles()}>
|
||||
<ArticleCardSwiper title={t('Favorite')} slides={topArticles()} />
|
||||
</Show>
|
||||
|
||||
<Beside
|
||||
beside={sortedArticles()[20]}
|
||||
title={t('Top topics')}
|
||||
|
@ -156,11 +147,8 @@ export const HomeView = (props: Props) => {
|
|||
isTopicCompact={true}
|
||||
nodate={true}
|
||||
/>
|
||||
|
||||
<Row3 articles={sortedArticles().slice(21, 24)} nodate={true} />
|
||||
|
||||
<Banner />
|
||||
|
||||
<Row2 articles={sortedArticles().slice(24, 26)} nodate={true} />
|
||||
<Row3 articles={sortedArticles().slice(26, 29)} nodate={true} />
|
||||
<Row2 articles={sortedArticles().slice(29, 31)} nodate={true} />
|
||||
|
|
|
@ -52,7 +52,7 @@ export const InboxView = () => {
|
|||
const [isClear, setClear] = createSignal(false)
|
||||
const { session } = useSession()
|
||||
const currentUserId = createMemo(() => session()?.user.id)
|
||||
const { changeSearchParam, searchParams } = useRouter<InboxSearchParams>()
|
||||
const { changeSearchParams, searchParams } = useRouter<InboxSearchParams>()
|
||||
// Поиск по диалогам
|
||||
const getQuery = (query) => {
|
||||
if (query().length >= 2) {
|
||||
|
@ -67,7 +67,7 @@ export const InboxView = () => {
|
|||
|
||||
const handleOpenChat = async (chat: Chat) => {
|
||||
setCurrentDialog(chat)
|
||||
changeSearchParam({
|
||||
changeSearchParams({
|
||||
chat: chat.id,
|
||||
})
|
||||
try {
|
||||
|
@ -126,7 +126,7 @@ export const InboxView = () => {
|
|||
try {
|
||||
const newChat = await createChat([Number(searchParams().initChat)], '')
|
||||
await loadChats()
|
||||
changeSearchParam({
|
||||
changeSearchParams({
|
||||
initChat: null,
|
||||
chat: newChat.chat.id,
|
||||
})
|
||||
|
|
|
@ -41,7 +41,7 @@ const LOAD_MORE_PAGE_SIZE = 9 // Row3 + Row3 + Row3
|
|||
|
||||
export const TopicView = (props: Props) => {
|
||||
const { t } = useLocalize()
|
||||
const { searchParams, changeSearchParam } = useRouter<TopicsPageSearchParams>()
|
||||
const { searchParams, changeSearchParams } = useRouter<TopicsPageSearchParams>()
|
||||
|
||||
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
|
||||
|
||||
|
@ -125,7 +125,7 @@ export const TopicView = (props: Props) => {
|
|||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
changeSearchParam({
|
||||
changeSearchParams({
|
||||
by: 'recent',
|
||||
})
|
||||
}
|
||||
|
@ -135,17 +135,17 @@ export const TopicView = (props: Props) => {
|
|||
</li>
|
||||
{/*TODO: server sort*/}
|
||||
{/*<li classList={{ 'view-switcher__item--selected': getSearchParams().by === 'rating' }}>*/}
|
||||
{/* <button type="button" onClick={() => changeSearchParam('by', 'rating')}>*/}
|
||||
{/* <button type="button" onClick={() => changeSearchParams('by', 'rating')}>*/}
|
||||
{/* {t('Popular')}*/}
|
||||
{/* </button>*/}
|
||||
{/*</li>*/}
|
||||
{/*<li classList={{ 'view-switcher__item--selected': getSearchParams().by === 'viewed' }}>*/}
|
||||
{/* <button type="button" onClick={() => changeSearchParam('by', 'viewed')}>*/}
|
||||
{/* <button type="button" onClick={() => changeSearchParams('by', 'viewed')}>*/}
|
||||
{/* {t('Views')}*/}
|
||||
{/* </button>*/}
|
||||
{/*</li>*/}
|
||||
{/*<li classList={{ 'view-switcher__item--selected': getSearchParams().by === 'commented' }}>*/}
|
||||
{/* <button type="button" onClick={() => changeSearchParam('by', 'commented')}>*/}
|
||||
{/* <button type="button" onClick={() => changeSearchParams('by', 'commented')}>*/}
|
||||
{/* {t('Discussing')}*/}
|
||||
{/* </button>*/}
|
||||
{/*</li>*/}
|
||||
|
|
7
src/components/_shared/DropDown/DropDown.module.scss
Normal file
7
src/components/_shared/DropDown/DropDown.module.scss
Normal file
|
@ -0,0 +1,7 @@
|
|||
.chevron {
|
||||
vertical-align: top;
|
||||
|
||||
&.rotate {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
69
src/components/_shared/DropDown/DropDown.tsx
Normal file
69
src/components/_shared/DropDown/DropDown.tsx
Normal file
|
@ -0,0 +1,69 @@
|
|||
import type { PopupProps } from '../Popup'
|
||||
|
||||
import { clsx } from 'clsx'
|
||||
import { createSignal, For, Show } from 'solid-js'
|
||||
|
||||
import { Popup } from '../Popup'
|
||||
|
||||
import styles from './DropDown.module.scss'
|
||||
|
||||
export type Option = {
|
||||
value: string | number
|
||||
title: string
|
||||
}
|
||||
|
||||
type Props<TOption> = {
|
||||
class?: string
|
||||
popupProps?: PopupProps
|
||||
options: TOption[]
|
||||
currentOption: TOption
|
||||
triggerCssClass?: string
|
||||
onChange: (option: TOption) => void
|
||||
}
|
||||
|
||||
const Chevron = (props: { class?: string }) => {
|
||||
return (
|
||||
<svg
|
||||
class={props.class}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
>
|
||||
<path d="M13.5 6L9 12L4.5 6H13.5Z" fill="#141414" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export const DropDown = <TOption extends Option = Option>(props: Props<TOption>) => {
|
||||
const [isPopupVisible, setIsPopupVisible] = createSignal(false)
|
||||
|
||||
return (
|
||||
<Show when={props.currentOption} keyed={true}>
|
||||
<Popup
|
||||
trigger={
|
||||
<div class={props.triggerCssClass}>
|
||||
{props.currentOption.title}{' '}
|
||||
<Chevron
|
||||
class={clsx(styles.chevron, {
|
||||
[styles.rotate]: isPopupVisible(),
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
variant="tiny"
|
||||
onVisibilityChange={(isVisible) => setIsPopupVisible(isVisible)}
|
||||
{...props.popupProps}
|
||||
>
|
||||
<For each={props.options.filter((p) => p.value !== props.currentOption.value)}>
|
||||
{(option) => (
|
||||
<div class="link" onClick={() => props.onChange(option)}>
|
||||
{option.title}
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</Popup>
|
||||
</Show>
|
||||
)
|
||||
}
|
1
src/components/_shared/DropDown/index.ts
Normal file
1
src/components/_shared/DropDown/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { DropDown } from './DropDown'
|
|
@ -32,7 +32,9 @@ export const Popup = (props: PopupProps) => {
|
|||
useOutsideClickHandler({
|
||||
containerRef,
|
||||
predicate: () => isVisible(),
|
||||
handler: () => setIsVisible(false),
|
||||
handler: () => {
|
||||
setIsVisible(false)
|
||||
},
|
||||
})
|
||||
|
||||
const toggle = () => setIsVisible((oldVisible) => !oldVisible)
|
||||
|
|
|
@ -32,7 +32,7 @@ export function useLocalize() {
|
|||
|
||||
export const LocalizeProvider = (props: { children: JSX.Element }) => {
|
||||
const [lang, setLang] = createSignal<Language>(i18next.language === 'en' ? 'en' : 'ru')
|
||||
const { searchParams, changeSearchParam } = useRouter<{
|
||||
const { searchParams, changeSearchParams } = useRouter<{
|
||||
lng: string
|
||||
}>()
|
||||
|
||||
|
@ -46,7 +46,7 @@ export const LocalizeProvider = (props: { children: JSX.Element }) => {
|
|||
changeLanguage(lng)
|
||||
setLang(lng)
|
||||
Cookie.set('lng', lng)
|
||||
changeSearchParam({ lng: null }, true)
|
||||
changeSearchParams({ lng: null }, true)
|
||||
})
|
||||
|
||||
const formatTime = (date: Date, options: Intl.DateTimeFormatOptions = {}) => {
|
||||
|
|
|
@ -12,7 +12,10 @@ import { loadMyFeed, loadShouts, resetSortedArticles } from '../stores/zine/arti
|
|||
const handleFeedLoadShouts = (options: LoadShoutsOptions) => {
|
||||
return loadShouts({
|
||||
...options,
|
||||
filters: { visibility: 'community' },
|
||||
filters: {
|
||||
visibility: 'community',
|
||||
...options.filters,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -138,7 +138,7 @@ export const useRouter = <TSearchParams extends Record<string, string> = Record<
|
|||
const page = useStore(routerStore)
|
||||
const searchParams = useStore(searchParamsStore) as unknown as Accessor<TSearchParams>
|
||||
|
||||
const changeSearchParam = (newValues: Partial<TSearchParams>, replace = false) => {
|
||||
const changeSearchParams = (newValues: Partial<TSearchParams>, replace = false) => {
|
||||
const newSearchParams = { ...searchParamsStore.get() }
|
||||
|
||||
Object.keys(newValues).forEach((key) => {
|
||||
|
@ -155,6 +155,6 @@ export const useRouter = <TSearchParams extends Record<string, string> = Record<
|
|||
return {
|
||||
page,
|
||||
searchParams,
|
||||
changeSearchParam,
|
||||
changeSearchParams,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,13 +42,13 @@ export const MODALS: Record<ModalType, ModalType> = {
|
|||
|
||||
const [modal, setModal] = createSignal<ModalType>(null)
|
||||
|
||||
const { searchParams, changeSearchParam } = useRouter<
|
||||
const { searchParams, changeSearchParams } = useRouter<
|
||||
AuthModalSearchParams & ConfirmEmailSearchParams & RootSearchParams
|
||||
>()
|
||||
|
||||
export const showModal = (modalType: ModalType, modalSource?: AuthModalSource) => {
|
||||
if (modalSource) {
|
||||
changeSearchParam({
|
||||
changeSearchParams({
|
||||
source: modalSource,
|
||||
})
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ export const hideModal = () => {
|
|||
newSearchParams.mode = null
|
||||
}
|
||||
|
||||
changeSearchParam(newSearchParams, true)
|
||||
changeSearchParams(newSearchParams, true)
|
||||
|
||||
setModal(null)
|
||||
}
|
||||
|
|
|
@ -204,7 +204,7 @@ a:hover,
|
|||
a:visited,
|
||||
a:link,
|
||||
.link {
|
||||
border-bottom: 1px solid rgb(0 0 0 / 30%);
|
||||
border-bottom: 2px solid rgb(0 0 0 / 30%);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -624,6 +624,10 @@ figure {
|
|||
margin-bottom: 0.6em;
|
||||
white-space: nowrap;
|
||||
|
||||
.link {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
@ -645,9 +649,10 @@ figure {
|
|||
}
|
||||
|
||||
a,
|
||||
.link,
|
||||
.linkReplacement,
|
||||
button {
|
||||
border-bottom: 2px solid transparent;
|
||||
border-bottom: 1px solid transparent;
|
||||
color: var(--link-color);
|
||||
cursor: pointer;
|
||||
font-weight: inherit;
|
||||
|
@ -662,6 +667,7 @@ figure {
|
|||
font-weight: bold;
|
||||
|
||||
a,
|
||||
.link,
|
||||
.linkReplacement,
|
||||
button {
|
||||
border-bottom: 2px solid #000;
|
||||
|
|
Loading…
Reference in New Issue
Block a user