Feature/unrated (#334)
* Rate first markup * WIP * lint * unrated articles + random top fixes --------- Co-authored-by: ilya-bkv <i.yablokov@ccmp.me> Co-authored-by: Igor Lobanov <igor.lobanov@onetwotrip.com>
This commit is contained in:
parent
e5846deab7
commit
d2977b9b21
|
@ -41,6 +41,7 @@
|
|||
"Back": "Back",
|
||||
"Back to editor": "Back to editor",
|
||||
"Back to main page": "Back to main page",
|
||||
"Be the first to rate": "Be the first to rate",
|
||||
"Become an author": "Become an author",
|
||||
"Bold": "Bold",
|
||||
"Bookmarked": "Saved",
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
"Back": "Назад",
|
||||
"Back to editor": "Вернуться в редактор",
|
||||
"Back to main page": "Вернуться на главную",
|
||||
"Be the first to rate": "Оцените первым",
|
||||
"Become an author": "Стать автором",
|
||||
"Bold": "Жирный",
|
||||
"Bookmarked": "Сохранено",
|
||||
|
|
|
@ -46,7 +46,7 @@ export type ArticleCardProps = {
|
|||
withViewed?: boolean
|
||||
noAuthorLink?: boolean
|
||||
}
|
||||
desktopCoverSize: 'XS' | 'S' | 'M' | 'L'
|
||||
desktopCoverSize?: 'XS' | 'S' | 'M' | 'L'
|
||||
article: Shout
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,12 @@ import { clsx } from 'clsx'
|
|||
import { createEffect, createMemo, createSignal, For, on, onCleanup, onMount, Show } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { LoadRandomTopShoutsParams, LoadShoutsOptions, Shout } from '../../../graphql/types.gen'
|
||||
import {
|
||||
LoadRandomTopShoutsParams,
|
||||
LoadShoutsFilters,
|
||||
LoadShoutsOptions,
|
||||
Shout,
|
||||
} from '../../../graphql/types.gen'
|
||||
import { LayoutType } from '../../../pages/types'
|
||||
import { router } from '../../../stores/router'
|
||||
import { loadShouts, resetSortedArticles, useArticlesStore } from '../../../stores/zine/articles'
|
||||
|
@ -24,7 +29,7 @@ type Props = {
|
|||
layout: LayoutType
|
||||
}
|
||||
|
||||
export const PRERENDERED_ARTICLES_COUNT = 32
|
||||
export const PRERENDERED_ARTICLES_COUNT = 24
|
||||
const LOAD_MORE_PAGE_SIZE = 16
|
||||
|
||||
export const Expo = (props: Props) => {
|
||||
|
@ -40,15 +45,26 @@ export const Expo = (props: Props) => {
|
|||
shouts: isLoaded() ? props.shouts : [],
|
||||
})
|
||||
|
||||
const getLoadShoutsFilters = (filters: LoadShoutsFilters = {}): LoadShoutsFilters => {
|
||||
const result = { ...filters }
|
||||
|
||||
if (props.layout) {
|
||||
filters.layout = props.layout
|
||||
} else {
|
||||
filters.excludeLayout = 'article'
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const loadMore = async (count: number) => {
|
||||
saveScrollPosition()
|
||||
const options: LoadShoutsOptions = {
|
||||
filters: getLoadShoutsFilters(),
|
||||
limit: count,
|
||||
offset: sortedArticles().length,
|
||||
}
|
||||
|
||||
options.filters = props.layout ? { layout: props.layout } : { excludeLayout: 'article' }
|
||||
|
||||
const { hasMore } = await loadShouts(options)
|
||||
setIsLoadMoreButtonVisible(hasMore)
|
||||
restoreScrollPosition()
|
||||
|
@ -56,13 +72,10 @@ export const Expo = (props: Props) => {
|
|||
|
||||
const loadRandomTopArticles = async () => {
|
||||
const params: LoadRandomTopShoutsParams = {
|
||||
filters: {
|
||||
visibility: 'public',
|
||||
},
|
||||
filters: getLoadShoutsFilters(),
|
||||
limit: 10,
|
||||
fromRandomCount: 100,
|
||||
}
|
||||
params.filters = props.layout ? { layout: props.layout } : { excludeLayout: 'article' }
|
||||
|
||||
const result = await apiClient.getRandomTopShouts(params)
|
||||
setRandomTopArticles(result)
|
||||
|
@ -73,14 +86,10 @@ export const Expo = (props: Props) => {
|
|||
const fromDate = getServerDate(new Date(now.setMonth(now.getMonth() - 1)))
|
||||
|
||||
const params: LoadRandomTopShoutsParams = {
|
||||
filters: {
|
||||
visibility: 'public',
|
||||
fromDate,
|
||||
},
|
||||
filters: getLoadShoutsFilters({ fromDate }),
|
||||
limit: 10,
|
||||
fromRandomCount: 10,
|
||||
}
|
||||
params.filters = props.layout ? { layout: props.layout } : { excludeLayout: 'article' }
|
||||
|
||||
const result = await apiClient.getRandomTopShouts(params)
|
||||
setRandomTopMonthArticles(result)
|
||||
|
@ -103,9 +112,7 @@ export const Expo = (props: Props) => {
|
|||
if (sortedArticles().length === PRERENDERED_ARTICLES_COUNT) {
|
||||
loadMore(LOAD_MORE_PAGE_SIZE)
|
||||
}
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
loadRandomTopArticles()
|
||||
loadRandomTopMonthArticles()
|
||||
})
|
||||
|
|
|
@ -1,32 +1,32 @@
|
|||
import type { Author, LoadShoutsOptions, Reaction, Shout } from '../../graphql/types.gen'
|
||||
import type { Author, LoadShoutsOptions, Reaction, Shout } from '../../../graphql/types.gen'
|
||||
|
||||
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 { useLocalize } from '../../context/localize'
|
||||
import { useReactions } from '../../context/reactions'
|
||||
import { router, useRouter } from '../../stores/router'
|
||||
import { useArticlesStore, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { useTopAuthorsStore } from '../../stores/zine/topAuthors'
|
||||
import { useTopicsStore } from '../../stores/zine/topics'
|
||||
import { capitalize } from '../../utils/capitalize'
|
||||
import { getImageUrl } from '../../utils/getImageUrl'
|
||||
import { getDescription } from '../../utils/meta'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
import { Loading } from '../_shared/Loading'
|
||||
import { CommentDate } from '../Article/CommentDate'
|
||||
import { AuthorLink } from '../Author/AhtorLink'
|
||||
import { AuthorBadge } from '../Author/AuthorBadge'
|
||||
import { ArticleCard } from '../Feed/ArticleCard'
|
||||
import { Sidebar } from '../Feed/Sidebar'
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { useReactions } from '../../../context/reactions'
|
||||
import { router, useRouter } from '../../../stores/router'
|
||||
import { useArticlesStore, resetSortedArticles } from '../../../stores/zine/articles'
|
||||
import { useTopAuthorsStore } from '../../../stores/zine/topAuthors'
|
||||
import { useTopicsStore } from '../../../stores/zine/topics'
|
||||
import { apiClient } from '../../../utils/apiClient'
|
||||
import { getImageUrl } from '../../../utils/getImageUrl'
|
||||
import { Icon } from '../../_shared/Icon'
|
||||
import { Loading } from '../../_shared/Loading'
|
||||
import { CommentDate } from '../../Article/CommentDate'
|
||||
import { AuthorLink } from '../../Author/AhtorLink'
|
||||
import { AuthorBadge } from '../../Author/AuthorBadge'
|
||||
import { ArticleCard } from '../../Feed/ArticleCard'
|
||||
import { Sidebar } from '../../Feed/Sidebar'
|
||||
|
||||
import styles from './Feed.module.scss'
|
||||
import stylesBeside from '../../components/Feed/Beside.module.scss'
|
||||
import stylesTopic from '../Feed/CardTopic.module.scss'
|
||||
import stylesBeside from '../../Feed/Beside.module.scss'
|
||||
import stylesTopic from '../../Feed/CardTopic.module.scss'
|
||||
|
||||
export const FEED_PAGE_SIZE = 20
|
||||
const UNRATED_ARTICLES_COUNT = 5
|
||||
|
||||
type FeedSearchParams = {
|
||||
by: 'publish_date' | 'rating' | 'last_comment'
|
||||
|
@ -51,7 +51,7 @@ type Props = {
|
|||
}>
|
||||
}
|
||||
|
||||
export const FeedView = (props: Props) => {
|
||||
export const Feed = (props: Props) => {
|
||||
const { t } = useLocalize()
|
||||
const { page, searchParams } = useRouter<FeedSearchParams>()
|
||||
const [isLoading, setIsLoading] = createSignal(false)
|
||||
|
@ -62,13 +62,20 @@ export const FeedView = (props: Props) => {
|
|||
const { topAuthors } = useTopAuthorsStore()
|
||||
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
|
||||
const [topComments, setTopComments] = createSignal<Reaction[]>([])
|
||||
const [unratedArticles, setUnratedArticles] = createSignal<Shout[]>([])
|
||||
|
||||
const {
|
||||
actions: { loadReactionsBy },
|
||||
} = useReactions()
|
||||
|
||||
const loadUnratedArticles = async () => {
|
||||
const result = await apiClient.getUnratedShouts(UNRATED_ARTICLES_COUNT)
|
||||
setUnratedArticles(result)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
loadMore()
|
||||
loadUnratedArticles()
|
||||
})
|
||||
|
||||
createEffect(
|
||||
|
@ -271,6 +278,14 @@ export const FeedView = (props: Props) => {
|
|||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<Show when={unratedArticles().length > 0}>
|
||||
<section class={clsx(styles.asideSection)}>
|
||||
<h4>{t('Be the first to rate')}</h4>
|
||||
<For each={unratedArticles()}>
|
||||
{(article) => <ArticleCard article={article} settings={{ noimage: true, nodate: true }} />}
|
||||
</For>
|
||||
</section>
|
||||
</Show>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
1
src/components/Views/Feed/index.ts
Normal file
1
src/components/Views/Feed/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { Feed } from './Feed'
|
|
@ -29,7 +29,7 @@ export const SearchView = (props: Props) => {
|
|||
|
||||
const { searchParams } = useRouter<SearchPageSearchParams>()
|
||||
let searchEl: HTMLInputElement
|
||||
const handleQueryChange = (_ev) => {
|
||||
const handleQueryChange = () => {
|
||||
setQuery(searchEl.value)
|
||||
}
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
padding: 0;
|
||||
|
||||
& swiper-slide {
|
||||
//bind to html element <swiper-slide/>
|
||||
// bind to html element <swiper-slide/>
|
||||
width: unset !important;
|
||||
}
|
||||
|
||||
|
|
46
src/graphql/query/articles-load-unrated.ts
Normal file
46
src/graphql/query/articles-load-unrated.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { gql } from '@urql/core'
|
||||
|
||||
export default gql`
|
||||
query LoadUnratedShoutsQuery($limit: Int!) {
|
||||
loadUnratedShouts(limit: $limit) {
|
||||
id
|
||||
title
|
||||
lead
|
||||
description
|
||||
subtitle
|
||||
slug
|
||||
layout
|
||||
cover
|
||||
lead
|
||||
# community
|
||||
mainTopic
|
||||
topics {
|
||||
id
|
||||
title
|
||||
body
|
||||
slug
|
||||
stat {
|
||||
shouts
|
||||
authors
|
||||
followers
|
||||
}
|
||||
}
|
||||
authors {
|
||||
id
|
||||
name
|
||||
slug
|
||||
userpic
|
||||
createdAt
|
||||
bio
|
||||
}
|
||||
createdAt
|
||||
publishedAt
|
||||
stat {
|
||||
viewed
|
||||
reacted
|
||||
rating
|
||||
commented
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
|
@ -377,6 +377,7 @@ export type Query = {
|
|||
loadRecipients: Result
|
||||
loadShout?: Maybe<Shout>
|
||||
loadShouts: Array<Maybe<Shout>>
|
||||
loadUnratedShouts: Array<Maybe<Shout>>
|
||||
markdownBody: Scalars['String']['output']
|
||||
myFeed?: Maybe<Array<Maybe<Shout>>>
|
||||
searchMessages: Result
|
||||
|
@ -449,6 +450,10 @@ export type QueryLoadShoutsArgs = {
|
|||
options?: InputMaybe<LoadShoutsOptions>
|
||||
}
|
||||
|
||||
export type QueryLoadUnratedShoutsArgs = {
|
||||
limit: Scalars['Int']['input']
|
||||
}
|
||||
|
||||
export type QueryMarkdownBodyArgs = {
|
||||
body: Scalars['String']['input']
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import { createEffect, createMemo, createSignal, on, onCleanup, onMount, Show }
|
|||
import { Loading } from '../components/_shared/Loading'
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
import { AuthorView, PRERENDERED_ARTICLES_COUNT } from '../components/Views/Author'
|
||||
import { useLocalize } from '../context/localize'
|
||||
import { ReactionsProvider } from '../context/reactions'
|
||||
import { useRouter } from '../stores/router'
|
||||
import { loadShouts, resetSortedArticles } from '../stores/zine/articles'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { PageProps } from '../types'
|
||||
|
||||
import { createMemo } from 'solid-js'
|
||||
import { createEffect, createMemo, on } from 'solid-js'
|
||||
|
||||
import { PageLayout } from '../../components/_shared/PageLayout'
|
||||
import { Topics } from '../../components/Nav/Topics'
|
||||
|
@ -14,7 +14,7 @@ export const ExpoPage = (props: PageProps) => {
|
|||
const { page } = useRouter()
|
||||
const getLayout = createMemo<LayoutType>(() => page().params['layout'] as LayoutType)
|
||||
|
||||
const title = createMemo(() => {
|
||||
const getTitle = () => {
|
||||
switch (getLayout()) {
|
||||
case 'music': {
|
||||
return t('Audio')
|
||||
|
@ -32,10 +32,20 @@ export const ExpoPage = (props: PageProps) => {
|
|||
return t('Art')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => getLayout(),
|
||||
() => {
|
||||
document.title = getTitle()
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
|
||||
return (
|
||||
<PageLayout withPadding={true} zeroBottomPadding={true} title={title()}>
|
||||
<PageLayout withPadding={true} zeroBottomPadding={true} title={getTitle()}>
|
||||
<Topics />
|
||||
<Expo shouts={props.expoShouts} layout={getLayout()} />
|
||||
</PageLayout>
|
||||
|
|
|
@ -2,7 +2,7 @@ import { createEffect, Match, on, onCleanup, Switch } from 'solid-js'
|
|||
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
import { AuthGuard } from '../components/AuthGuard'
|
||||
import { FeedView } from '../components/Views/Feed'
|
||||
import { Feed } from '../components/Views/Feed'
|
||||
import { useLocalize } from '../context/localize'
|
||||
import { ReactionsProvider } from '../context/reactions'
|
||||
import { LoadShoutsOptions } from '../graphql/types.gen'
|
||||
|
@ -40,13 +40,13 @@ export const FeedPage = () => {
|
|||
return (
|
||||
<PageLayout title={t('Feed')}>
|
||||
<ReactionsProvider>
|
||||
<Switch fallback={<FeedView loadShouts={handleFeedLoadShouts} />}>
|
||||
<Switch fallback={<Feed loadShouts={handleFeedLoadShouts} />}>
|
||||
<Match when={page().route === 'feed'}>
|
||||
<FeedView loadShouts={handleFeedLoadShouts} />
|
||||
<Feed loadShouts={handleFeedLoadShouts} />
|
||||
</Match>
|
||||
<Match when={page().route === 'feedMy'}>
|
||||
<AuthGuard>
|
||||
<FeedView loadShouts={handleMyFeedLoadShouts} />
|
||||
<Feed loadShouts={handleMyFeedLoadShouts} />
|
||||
</AuthGuard>
|
||||
</Match>
|
||||
</Switch>
|
||||
|
|
|
@ -4,16 +4,13 @@ import { createEffect, createMemo, createSignal, on, onCleanup, onMount } from '
|
|||
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
import { PRERENDERED_ARTICLES_COUNT, TopicView } from '../components/Views/Topic'
|
||||
import { useLocalize } from '../context/localize'
|
||||
import { ReactionsProvider } from '../context/reactions'
|
||||
import { useRouter } from '../stores/router'
|
||||
import { loadShouts, resetSortedArticles } from '../stores/zine/articles'
|
||||
import { loadTopic } from '../stores/zine/topics'
|
||||
import { capitalize } from '../utils/capitalize'
|
||||
|
||||
export const TopicPage = (props: PageProps) => {
|
||||
const { page } = useRouter()
|
||||
const { t } = useLocalize()
|
||||
const slug = createMemo(() => page().params['slug'] as string)
|
||||
|
||||
const [isLoaded, setIsLoaded] = createSignal(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { Author, Shout, ShoutInput, LoadShoutsOptions } from '../../graphql/types.gen'
|
||||
import type { Author, Shout, LoadShoutsOptions } from '../../graphql/types.gen'
|
||||
|
||||
import { createLazyMemo } from '@solid-primitives/memo'
|
||||
import { createSignal } from 'solid-js'
|
||||
|
@ -179,14 +179,6 @@ export const resetSortedArticles = () => {
|
|||
setSortedArticles([])
|
||||
}
|
||||
|
||||
export const createArticle = async ({ article }: { article: ShoutInput }) => {
|
||||
try {
|
||||
await apiClient.createArticle({ article })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
type InitialState = {
|
||||
shouts?: Shout[]
|
||||
}
|
||||
|
|
|
@ -44,8 +44,8 @@ import { getToken, privateGraphQLClient } from '../graphql/privateGraphQLClient'
|
|||
import { publicGraphQLClient } from '../graphql/publicGraphQLClient'
|
||||
import shoutLoad from '../graphql/query/article-load'
|
||||
import shoutsLoadBy from '../graphql/query/articles-load-by'
|
||||
import shoutsLoadRandomTop from '../graphql/query/articles-load-random-top'
|
||||
import articlesLoadRandomTop from '../graphql/query/articles-load-random-top'
|
||||
import articlesLoadUnrated from '../graphql/query/articles-load-unrated'
|
||||
import authCheckEmailQuery from '../graphql/query/auth-check-email'
|
||||
import authLoginQuery from '../graphql/query/auth-login'
|
||||
import authorBySlug from '../graphql/query/author-by-slug'
|
||||
|
@ -362,6 +362,15 @@ export const apiClient = {
|
|||
return resp.data.loadRandomTopShouts
|
||||
},
|
||||
|
||||
getUnratedShouts: async (limit: number): Promise<Shout[]> => {
|
||||
const resp = await publicGraphQLClient.query(articlesLoadUnrated, { limit }).toPromise()
|
||||
if (resp.error) {
|
||||
console.error(resp)
|
||||
}
|
||||
|
||||
return resp.data.loadUnratedShouts
|
||||
},
|
||||
|
||||
getMyFeed: async (options: LoadShoutsOptions) => {
|
||||
const resp = await privateGraphQLClient.query(myFeed, { options }).toPromise()
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user