This commit is contained in:
tonyrewin 2022-11-15 17:24:50 +03:00
parent a9a22cd7f6
commit 39aec90013
33 changed files with 137 additions and 441 deletions

View File

@ -1,3 +1,9 @@
[0.6.1]
[+] auth ver. 0.9
[+] load-by interfaces for shouts, authors and messages
[+] inbox logix and markup
[-] old views counting
[0.6.0]
[+] hybrid routing ssr/spa
[+] 'expo' pages

View File

@ -1,6 +1,6 @@
{
"name": "discoursio-webapp",
"version": "0.6.0",
"version": "0.6.1",
"private": true,
"license": "MIT",
"scripts": {

View File

@ -1,5 +1,5 @@
import { createEffect, createMemo, createSignal, onMount } from 'solid-js'
import { For } from 'solid-js/web'
import { createEffect, createMemo, createSignal, onMount, For } from 'solid-js'
import type { Shout } from '../../graphql/types.gen'
import { Soundwave } from './Soundwave'
@ -40,7 +40,7 @@ export default (props: { shout: Shout }) => {
<div class="audio-player-list">
<div class="player current-track">
<div class="player-title">{currentTrack().title}</div>
<i class="fas fa-pause fa-3x fa-fw" onClick={togglePlayPause}></i>
<i class="fas fa-pause fa-3x fa-fw" onClick={togglePlayPause} />
<div class="player-progress">
<Soundwave context={audioContext()} url={currentTrack().src} />
<span class="track-position">{`${audioRef.currentTime} / ${audioRef.duration}`}</span>
@ -53,7 +53,7 @@ export default (props: { shout: Shout }) => {
{(m: MediaItem) => (
<li>
<div class="player-status">
<i class="fas fa-play fa-fw" onClick={() => playMedia(m)}></i>
<i class="fas fa-play fa-fw" onClick={() => playMedia(m)} />
</div>
<span class="track-title">{m.title}</span>
</li>

View File

@ -7,7 +7,6 @@ import { createMemo, For, onMount, Show } from 'solid-js'
import type { Author, Reaction, Shout } from '../../graphql/types.gen'
import { t } from '../../utils/intl'
import { showModal } from '../../stores/ui'
import { incrementView } from '../../stores/zine/articles'
import MD from './MD'
import { SharePopup } from './SharePopup'
import { useSession } from '../../context/session'
@ -39,11 +38,6 @@ const formatDate = (date: Date) => {
export const FullArticle = (props: ArticleProps) => {
const { session } = useSession()
onMount(() => {
incrementView({ articleSlug: props.article.slug })
})
const formattedDate = createMemo(() => formatDate(new Date(props.article.createdAt)))
const mainTopic = () =>

View File

@ -105,5 +105,5 @@ export const Soundwave = (props: SoundwaveProps) => {
onMount(() => {
drawAudio(props.context, props.url)
})
return <canvas ref={canvasRef}></canvas>
return <canvas ref={canvasRef} />
}

View File

@ -28,10 +28,12 @@
text-overflow: ellipsis;
white-space: nowrap;
}
.name {
color: #141414;
font-weight: 500;
}
.message {
color: #9fa1a7;
}
@ -40,6 +42,7 @@
.activity {
font-size: 12px;
margin-left: 12px;
.time {
text-align: right;
color: #ccc;

View File

@ -1,13 +1,10 @@
import './DialogCard.module.scss'
import styles from './DialogCard.module.scss'
import DialogAvatar from './DialogAvatar'
import type { Author } from '../../graphql/types.gen'
// import { useAuthStore } from '../../stores/auth'
import { createEffect, createSignal } from 'solid-js'
import type { Author, AuthResult } from '../../graphql/types.gen'
import { useSession } from '../../context/session'
import { createMemo, InitializedResource } from 'solid-js'
import { apiClient } from '../../utils/apiClient'
const { session } = useAuthStore()
type DialogProps = {
online?: boolean
message?: string
@ -20,20 +17,18 @@ const createChat = async ({ title, members }: { title?: string; members?: string
}
const DialogCard = (props: DialogProps) => {
const [currentUser, setCurrentUser] = createSignal(undefined)
createEffect(() => {
setCurrentUser(session()?.user?.slug)
})
const { session } = useSession()
const currentSession = createMemo<AuthResult>(() => session)
const handleOpenChat = async () => {
try {
const test = await apiClient.createChat({
title: 'test chat',
members: [props.author.slug, currentUser()]
members: [props.author.slug, currentSession().user.slug]
})
console.log('!!! test:', test)
} catch (err) {
console.log('!!! errr:', err)
} catch (error) {
console.log('!!! errr:', error)
}
}

View File

@ -1,7 +1,7 @@
.Search {
.field {
position: relative;
background: #ffffff;
background: #fff;
border: 2px solid #e8e8e8;
border-radius: 2px;
overflow: hidden;
@ -22,8 +22,10 @@
color: #858585;
font-family: inherit;
}
&:focus {
outline: none;
& + .icon {
opacity: 0;
right: -30px;

View File

@ -18,7 +18,6 @@ export const ProfileModal = () => {
const author = createMemo<Author>(() => {
const a: Author = {
id: null,
name: 'anonymous',
userpic: '',
slug: ''

View File

@ -1,14 +1,14 @@
import { PageWrap } from '../_shared/PageWrap'
import { ArticleView } from '../Views/Article'
import type { PageProps } from '../types'
import { loadArticle, useArticlesStore } from '../../stores/zine/articles'
import { loadShoutsBy, useArticlesStore } from '../../stores/zine/articles'
import { createMemo, onMount, Show } from 'solid-js'
import type { Shout } from '../../graphql/types.gen'
import { useRouter } from '../../stores/router'
import { Loading } from '../Loading'
export const ArticlePage = (props: PageProps) => {
const sortedArticles = props.article ? [props.article] : []
const shouts = props.article ? [props.article] : []
const slug = createMemo(() => {
const { page: getPage } = useRouter()
@ -23,16 +23,16 @@ export const ArticlePage = (props: PageProps) => {
})
const { articleEntities } = useArticlesStore({
sortedArticles
shouts
})
const article = createMemo<Shout>(() => articleEntities()[slug()])
onMount(() => {
onMount(async () => {
const articleValue = articleEntities()[slug()]
if (!articleValue || !articleValue.body) {
loadArticle({ slug: slug() })
await loadShoutsBy({ by: { slug: slug() }, limit: 1, offset: 0 })
}
})

View File

@ -2,7 +2,7 @@ import { PageWrap } from '../_shared/PageWrap'
import { AuthorView, PRERENDERED_ARTICLES_COUNT } from '../Views/Author'
import type { PageProps } from '../types'
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
import { loadAuthorArticles, resetSortedArticles } from '../../stores/zine/articles'
import { loadShoutsBy, resetSortedArticles } from '../../stores/zine/articles'
import { useRouter } from '../../stores/router'
import { loadAuthor } from '../../stores/zine/authors'
import { Loading } from '../Loading'
@ -27,7 +27,7 @@ export const AuthorPage = (props: PageProps) => {
return
}
await loadAuthorArticles({ authorSlug: slug(), limit: PRERENDERED_ARTICLES_COUNT })
await loadShoutsBy({ by: { author: slug() }, limit: PRERENDERED_ARTICLES_COUNT })
await loadAuthor({ slug: slug() })
setIsLoaded(true)
@ -38,7 +38,7 @@ export const AuthorPage = (props: PageProps) => {
return (
<PageWrap>
<Show when={isLoaded()} fallback={<Loading />}>
<AuthorView author={props.author} authorArticles={props.shouts} authorSlug={slug()} />
<AuthorView author={props.author} shouts={props.shouts} authorSlug={slug()} />
</Show>
</PageWrap>
)

View File

@ -2,7 +2,7 @@ import { HomeView, PRERENDERED_ARTICLES_COUNT } from '../Views/Home'
import { PageWrap } from '../_shared/PageWrap'
import type { PageProps } from '../types'
import { createSignal, onCleanup, onMount, Show } from 'solid-js'
import { loadPublishedArticles, resetSortedArticles } from '../../stores/zine/articles'
import { loadShoutsBy, resetSortedArticles } from '../../stores/zine/articles'
import { loadRandomTopics } from '../../stores/zine/topics'
import { Loading } from '../Loading'
@ -14,7 +14,7 @@ export const HomePage = (props: PageProps) => {
return
}
await loadPublishedArticles({ limit: PRERENDERED_ARTICLES_COUNT, offset: 0 })
await loadShoutsBy({ by: { visibility: 'public' }, limit: PRERENDERED_ARTICLES_COUNT, offset: 0 })
await loadRandomTopics()
setIsLoaded(true)
@ -25,7 +25,7 @@ export const HomePage = (props: PageProps) => {
return (
<PageWrap>
<Show when={isLoaded()} fallback={<Loading />}>
<HomeView randomTopics={props.randomTopics} recentPublishedArticles={props.shouts || []} />
<HomeView randomTopics={props.randomTopics} shouts={props.shouts || []} />
</Show>
</PageWrap>
)

View File

@ -2,7 +2,7 @@ import { PageWrap } from '../_shared/PageWrap'
import { SearchView } from '../Views/Search'
import type { PageProps } from '../types'
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
import { loadSearchResults, resetSortedArticles } from '../../stores/zine/articles'
import { loadShoutsBy, resetSortedArticles } from '../../stores/zine/articles'
import { useRouter } from '../../stores/router'
import { Loading } from '../Loading'
@ -26,7 +26,7 @@ export const SearchPage = (props: PageProps) => {
return
}
await loadSearchResults({ query: q(), limit: 50, offset: 0 })
await loadShoutsBy({ by: { title: q(), body: q() }, limit: 50, offset: 0 })
setIsLoaded(true)
})

View File

@ -2,7 +2,7 @@ import { PageWrap } from '../_shared/PageWrap'
import { PRERENDERED_ARTICLES_COUNT, TopicView } from '../Views/Topic'
import type { PageProps } from '../types'
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
import { loadTopicArticles, resetSortedArticles } from '../../stores/zine/articles'
import { loadShoutsBy, resetSortedArticles } from '../../stores/zine/articles'
import { useRouter } from '../../stores/router'
import { loadTopic } from '../../stores/zine/topics'
import { Loading } from '../Loading'
@ -27,7 +27,7 @@ export const TopicPage = (props: PageProps) => {
return
}
await loadTopicArticles({ topicSlug: slug(), limit: PRERENDERED_ARTICLES_COUNT, offset: 0 })
await loadShoutsBy({ by: { topics: [slug()] }, limit: PRERENDERED_ARTICLES_COUNT, offset: 0 })
await loadTopic({ slug: slug() })
setIsLoaded(true)
@ -38,7 +38,7 @@ export const TopicPage = (props: PageProps) => {
return (
<PageWrap>
<Show when={isLoaded()} fallback={<Loading />}>
<TopicView topic={props.topic} topicArticles={props.shouts} topicSlug={slug()} />
<TopicView topic={props.topic} shouts={props.shouts} topicSlug={slug()} />
</Show>
</PageWrap>
)

View File

@ -5,7 +5,7 @@ import { Row3 } from '../Feed/Row3'
import { AuthorFull } from '../Author/Full'
import { t } from '../../utils/intl'
import { useAuthorsStore } from '../../stores/zine/authors'
import { loadAuthorArticles, useArticlesStore } from '../../stores/zine/articles'
import { loadShoutsBy, useArticlesStore } from '../../stores/zine/articles'
import { useTopicsStore } from '../../stores/zine/topics'
import { useRouter } from '../../stores/router'
@ -15,7 +15,7 @@ import { splitToPages } from '../../utils/splitToPages'
// TODO: load reactions on client
type AuthorProps = {
authorArticles: Shout[]
shouts: Shout[]
author: Author
authorSlug: string
// FIXME author topics fro server
@ -31,7 +31,7 @@ const LOAD_MORE_PAGE_SIZE = 9 // Row3 + Row3 + Row3
export const AuthorView = (props: AuthorProps) => {
const { sortedArticles } = useArticlesStore({
sortedArticles: props.authorArticles
shouts: props.shouts
})
const { authorEntities } = useAuthorsStore({ authors: [props.author] })
const { topicsByAuthor } = useTopicsStore()
@ -42,8 +42,8 @@ export const AuthorView = (props: AuthorProps) => {
const loadMore = async () => {
saveScrollPosition()
const { hasMore } = await loadAuthorArticles({
authorSlug: author().slug,
const { hasMore } = await loadShoutsBy({
by: { author: author().slug },
limit: LOAD_MORE_PAGE_SIZE,
offset: sortedArticles().length
})

View File

@ -9,7 +9,7 @@ import { AuthorCard } from '../Author/Card'
import { t } from '../../utils/intl'
import { FeedSidebar } from '../Feed/Sidebar'
import CommentCard from '../Article/Comment'
import { loadRecentArticles, useArticlesStore } from '../../stores/zine/articles'
import { loadShoutsBy, useArticlesStore } from '../../stores/zine/articles'
import { useReactionsStore } from '../../stores/zine/reactions'
import { useAuthorsStore } from '../../stores/zine/authors'
import { useTopicsStore } from '../../stores/zine/topics'
@ -52,7 +52,11 @@ export const FeedView = () => {
// })
const loadMore = async () => {
const { hasMore } = await loadRecentArticles({ limit: FEED_PAGE_SIZE, offset: sortedArticles().length })
const { hasMore } = await loadShoutsBy({
by: { visibility: 'community' },
limit: FEED_PAGE_SIZE,
offset: sortedArticles().length
})
setIsLoadMoreButtonVisible(hasMore)
}

View File

@ -14,12 +14,7 @@ import type { Shout, Topic } from '../../graphql/types.gen'
import { Icon } from '../_shared/Icon'
import { t } from '../../utils/intl'
import { useTopicsStore } from '../../stores/zine/topics'
import {
loadPublishedArticles,
loadTopArticles,
loadTopMonthArticles,
useArticlesStore
} from '../../stores/zine/articles'
import { loadShoutsBy, useArticlesStore } from '../../stores/zine/articles'
import { useTopAuthorsStore } from '../../stores/zine/topAuthors'
import { locale } from '../../stores/ui'
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
@ -27,7 +22,7 @@ import { splitToPages } from '../../utils/splitToPages'
type HomeProps = {
randomTopics: Topic[]
recentPublishedArticles: Shout[]
shouts: Shout[]
}
export const PRERENDERED_ARTICLES_COUNT = 5
@ -37,26 +32,24 @@ const LOAD_MORE_PAGE_SIZE = 16 // Row1 + Row3 + Row2 + Beside (3 + 1) + Row1 + R
export const HomeView = (props: HomeProps) => {
const {
sortedArticles,
articlesByLayout,
topArticles,
topMonthArticles,
topViewedArticles,
topCommentedArticles,
articlesByLayout
topMonthArticles,
topViewedArticles
} = useArticlesStore({
sortedArticles: props.recentPublishedArticles
shouts: props.shouts
})
const { randomTopics, topTopics } = useTopicsStore({
randomTopics: props.randomTopics
})
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
const { topAuthors } = useTopAuthorsStore()
onMount(async () => {
loadTopArticles()
loadTopMonthArticles()
if (sortedArticles().length < PRERENDERED_ARTICLES_COUNT + CLIENT_LOAD_ARTICLES_COUNT) {
const { hasMore } = await loadPublishedArticles({
const { hasMore } = await loadShoutsBy({
by: {},
limit: CLIENT_LOAD_ARTICLES_COUNT,
offset: sortedArticles().length
})
@ -91,7 +84,8 @@ export const HomeView = (props: HomeProps) => {
const loadMore = async () => {
saveScrollPosition()
const { hasMore } = await loadPublishedArticles({
const { hasMore } = await loadShoutsBy({
by: { visibility: 'public' },
limit: LOAD_MORE_PAGE_SIZE,
offset: sortedArticles().length
})

View File

@ -3,7 +3,7 @@ import '../../styles/Search.scss'
import type { Shout } from '../../graphql/types.gen'
import { ArticleCard } from '../Feed/Card'
import { t } from '../../utils/intl'
import { useArticlesStore, loadSearchResults } from '../../stores/zine/articles'
import { useArticlesStore, loadShoutsBy } from '../../stores/zine/articles'
import { handleClientRouteLinkClick, useRouter } from '../../stores/router'
type SearchPageSearchParams = {
@ -16,7 +16,7 @@ type Props = {
}
export const SearchView = (props: Props) => {
const { sortedArticles } = useArticlesStore({ sortedArticles: props.results })
const { sortedArticles } = useArticlesStore({ shouts: props.results })
const [getQuery, setQuery] = createSignal(props.query)
const { searchParams } = useRouter<SearchPageSearchParams>()
@ -28,7 +28,7 @@ export const SearchView = (props: Props) => {
const handleSubmit = (_ev) => {
// TODO page
// TODO sort
loadSearchResults({ query: getQuery() })
loadShoutsBy({ by: { title: getQuery(), body: getQuery() }, limit: 50 })
}
return (

View File

@ -8,7 +8,7 @@ import { FullTopic } from '../Topic/Full'
import { t } from '../../utils/intl'
import { useRouter } from '../../stores/router'
import { useTopicsStore } from '../../stores/zine/topics'
import { loadTopicArticles, useArticlesStore } from '../../stores/zine/articles'
import { loadShoutsBy, useArticlesStore } from '../../stores/zine/articles'
import { useAuthorsStore } from '../../stores/zine/authors'
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
import { splitToPages } from '../../utils/splitToPages'
@ -22,7 +22,7 @@ type TopicsPageSearchParams = {
interface TopicProps {
topic: Topic
topicArticles: Shout[]
shouts: Shout[]
topicSlug: string
}
@ -34,7 +34,7 @@ export const TopicView = (props: TopicProps) => {
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
const { sortedArticles } = useArticlesStore({ sortedArticles: props.topicArticles })
const { sortedArticles } = useArticlesStore({ shouts: props.shouts })
const { topicEntities } = useTopicsStore({ topics: [props.topic] })
const { authorsByTopic } = useAuthorsStore()
@ -44,8 +44,8 @@ export const TopicView = (props: TopicProps) => {
const loadMore = async () => {
saveScrollPosition()
const { hasMore } = await loadTopicArticles({
topicSlug: topic().slug,
const { hasMore } = await loadShoutsBy({
by: { topic: topic().slug },
limit: LOAD_MORE_PAGE_SIZE,
offset: sortedArticles().length
})

View File

@ -1,9 +0,0 @@
import { gql } from '@urql/core'
export default gql`
mutation IncrementViewMutation($shout: String!) {
incrementView(shout: $shout) {
error
}
}
`

View File

@ -494,6 +494,7 @@ export type ReactionBy = {
days?: InputMaybe<Scalars['Int']>
order?: InputMaybe<Scalars['String']>
shout?: InputMaybe<Scalars['String']>
shouts?: InputMaybe<Array<InputMaybe<Scalars['String']>>>
stat?: InputMaybe<Scalars['String']>
topic?: InputMaybe<Scalars['String']>
}
@ -618,6 +619,7 @@ export type ShoutsBy = {
stat?: InputMaybe<Scalars['String']>
title?: InputMaybe<Scalars['String']>
topic?: InputMaybe<Scalars['String']>
topics?: InputMaybe<Array<InputMaybe<Scalars['String']>>>
visibility?: InputMaybe<Scalars['String']>
}

View File

@ -9,7 +9,7 @@ if (slug.endsWith('.map')) {
return Astro.redirect('/404')
}
const article = await apiClient.getArticle({ slug })
const article = await apiClient.loadShoutsBy({ by: { slug }, amount: 1})
if (!article) {
return Astro.redirect('/404')
}
@ -21,5 +21,5 @@ Astro.response.headers.set('Cache-Control', 's-maxage=1, stale-while-revalidate'
---
<Prerendered>
<Root article={article} client:load />
<Root article={article.at(0)} client:load />
</Prerendered>

View File

@ -6,8 +6,8 @@ import { initRouter } from '../../../stores/router'
import { PRERENDERED_ARTICLES_COUNT } from '../../../components/Views/Author'
const slug = Astro.params.slug.toString()
const shouts = await apiClient.getArticlesForAuthors({ authorSlugs: [slug], limit: PRERENDERED_ARTICLES_COUNT })
const author = await apiClient.getAuthor({ slug })
const shouts = await apiClient.loadShoutsBy({ by: { authors: [slug] } , amount: PRERENDERED_ARTICLES_COUNT })
const author = await apiClient.loadAuthorsBy({ by: { slug } })
const { pathname, search } = Astro.url
initRouter(pathname, search)

View File

@ -10,7 +10,7 @@ const layout = (Astro.params.layout?.toString() || 'article') as LayoutType
if (!layout || layout.endsWith('.map')) {
return Astro.redirect('/404')
}
const shouts = await apiClient.getRecentLayoutShouts({ layout })
const shouts = await apiClient.loadShoutsBy({ by: { layout } })
const { pathname, search } = Astro.url
initRouter(pathname, search)
---

View File

@ -6,7 +6,8 @@ import { initRouter } from '../stores/router'
import { PRERENDERED_ARTICLES_COUNT } from '../components/Views/Home'
const randomTopics = await apiClient.getRandomTopics({ amount: 12 })
const articles = await apiClient.getRecentPublishedArticles({ limit: PRERENDERED_ARTICLES_COUNT })
const articles = await apiClient.loadShoutsBy(
{ by: { visibility: "public" }, amount: PRERENDERED_ARTICLES_COUNT, offset: 0 })
const { pathname, search } = Astro.url
initRouter(pathname, search)

View File

@ -6,7 +6,7 @@ import { initRouter } from '../stores/router'
const params: URLSearchParams = Astro.url.searchParams
const q = params.get('q')
const searchResults = await apiClient.getSearchResults({ query: q, limit: 50 })
const searchResults = await apiClient.loadShoutsBy({ by: { title: q, body: q }, amount: 50 })
const { pathname, search } = Astro.url
initRouter(pathname, search)

View File

@ -5,7 +5,7 @@ import { apiClient } from '../../utils/apiClient'
import { PRERENDERED_ARTICLES_COUNT } from '../../components/Views/Topic'
const slug = Astro.params.slug?.toString() || ''
const shouts = await apiClient.getArticlesForTopics({ topicSlugs: [slug], limit: PRERENDERED_ARTICLES_COUNT })
const shouts = await apiClient.loadShoutsBy({ by: { topics: [slug] }, amount: PRERENDERED_ARTICLES_COUNT })
const topic = await apiClient.getTopic({ slug })
import { initRouter } from '../../stores/router'

View File

@ -1,4 +1,4 @@
import type { Author, Shout, ShoutInput, Topic } from '../../graphql/types.gen'
import type { Author, Shout, ShoutInput, ShoutsBy, Topic } from '../../graphql/types.gen'
import { apiClient } from '../../utils/apiClient'
import { addAuthorsByTopic } from './authors'
import { addTopicsByAuthor } from './topics'
@ -123,96 +123,18 @@ const addSortedArticles = (articles: Shout[]) => {
setSortedArticles((prevSortedArticles) => [...prevSortedArticles, ...articles])
}
export const loadFeed = async ({
limit,
offset
}: {
limit: number
offset?: number
}): Promise<{ hasMore: boolean }> => {
// TODO: load actual feed
return await loadRecentArticles({ limit, offset })
}
export const loadRecentArticles = async ({
limit,
offset
}: {
limit: number
offset?: number
}): Promise<{ hasMore: boolean }> => {
const newArticles = await apiClient.getRecentArticles({ limit: limit + 1, offset })
const hasMore = newArticles.length === limit + 1
if (hasMore) {
newArticles.splice(-1)
}
addArticles(newArticles)
addSortedArticles(newArticles)
return { hasMore }
}
export const loadPublishedArticles = async ({
export const loadShoutsBy = async ({
by,
limit,
offset = 0
}: {
by: ShoutsBy
limit: number
offset?: number
}): Promise<{ hasMore: boolean }> => {
const newArticles = await apiClient.getPublishedArticles({ limit: limit + 1, offset })
const hasMore = newArticles.length === limit + 1
if (hasMore) {
newArticles.splice(-1)
}
addArticles(newArticles)
addSortedArticles(newArticles)
return { hasMore }
}
export const loadAuthorArticles = async ({
authorSlug,
limit,
offset = 0
}: {
authorSlug: string
limit: number
offset?: number
}): Promise<{ hasMore: boolean }> => {
const newArticles = await apiClient.getArticlesForAuthors({
authorSlugs: [authorSlug],
limit: limit + 1,
offset
})
const hasMore = newArticles.length === limit + 1
if (hasMore) {
newArticles.splice(-1)
}
addArticles(newArticles)
addSortedArticles(newArticles)
return { hasMore }
}
export const loadTopicArticles = async ({
topicSlug,
limit,
offset
}: {
topicSlug: string
limit: number
offset: number
}): Promise<{ hasMore: boolean }> => {
const newArticles = await apiClient.getArticlesForTopics({
topicSlugs: [topicSlug],
limit: limit + 1,
const newArticles = await apiClient.loadShoutsBy({
by,
amount: limit + 1,
offset
})
@ -232,46 +154,6 @@ export const resetSortedArticles = () => {
setSortedArticles([])
}
export const loadTopMonthArticles = async (): Promise<void> => {
const articles = await apiClient.getTopMonthArticles()
addArticles(articles)
setTopMonthArticles(articles)
}
export const loadTopArticles = async (): Promise<void> => {
const articles = await apiClient.getTopArticles()
addArticles(articles)
setTopArticles(articles)
}
export const loadSearchResults = async ({
query,
limit,
offset
}: {
query: string
limit?: number
offset?: number
}): Promise<void> => {
const newArticles = await apiClient.getSearchResults({ query, limit, offset })
addArticles(newArticles)
addSortedArticles(newArticles)
}
export const incrementView = async ({ articleSlug }: { articleSlug: string }): Promise<void> => {
await apiClient.incrementView({ articleSlug })
}
export const loadArticle = async ({ slug }: { slug: string }): Promise<void> => {
const article = await apiClient.getArticle({ slug })
if (!article) {
throw new Error(`Can't load article, slug: "${slug}"`)
}
addArticles([article])
}
export const createArticle = async ({ article }: { article: ShoutInput }) => {
try {
await apiClient.createArticle({ article })
@ -281,27 +163,26 @@ export const createArticle = async ({ article }: { article: ShoutInput }) => {
}
type InitialState = {
sortedArticles?: Shout[]
topRatedArticles?: Shout[]
topRatedMonthArticles?: Shout[]
shouts?: Shout[]
}
export const useArticlesStore = (initialState: InitialState = {}) => {
addArticles([...(initialState.sortedArticles || [])])
addArticles([...(initialState.shouts || [])])
if (initialState.sortedArticles) {
setSortedArticles([...initialState.sortedArticles])
if (initialState.shouts) {
setSortedArticles([...initialState.shouts])
}
return {
articleEntities,
sortedArticles,
articlesByTopic,
loadShoutsBy,
articlesByAuthor,
topArticles,
articlesByLayout,
articlesByTopic,
topMonthArticles,
topViewedArticles,
topArticles,
topCommentedArticles,
articlesByLayout
topViewedArticles
}
}

View File

@ -32,11 +32,11 @@ export const loadRecentLayoutShouts = async ({
amount: number
offset?: number
}): Promise<{ hasMore: boolean }> => {
const layoutShouts: Shout[] = await apiClient.getRecentLayoutShouts({ layout, amount, offset })
const layoutShouts: Shout[] = await apiClient.loadShoutsBy({ by: { layout }, amount, offset })
const hasMore = layoutShouts.length < amount
if (hasMore) layoutShouts.splice(-1)
const sortedArticles = layoutShouts.sort(byCreated)
const { articlesByLayout } = useArticlesStore({ sortedArticles })
const shouts = layoutShouts.sort(byCreated)
const { articlesByLayout } = useArticlesStore({ shouts })
addLayoutShouts(layout, articlesByLayout()[layout])
return { hasMore }
}
@ -46,7 +46,7 @@ export const loadTopMonthLayoutShouts = async (
amount: number,
offset: number
): Promise<{ hasMore: boolean }> => {
const shouts = await apiClient.getTopMonthLayoutShouts({ layout })
const shouts = await apiClient.loadShoutsBy({ by: { layout, stat: 'rating', days: 30 } })
const hasMore = shouts.length < amount
if (hasMore) shouts.splice(-1)
addLayoutShouts(layout, shouts)
@ -58,14 +58,14 @@ export const loadTopLayoutShouts = async (
amount,
offset
): Promise<{ hasMore: boolean }> => {
const shouts = await apiClient.getTopLayoutShouts({ layout })
const shouts = await apiClient.loadShoutsBy({ by: { layout, stat: 'rating' } })
const hasMore = shouts.length < amount
if (hasMore) shouts.splice(-1)
addLayoutShouts(layout, shouts)
return { hasMore }
}
export const loadSearchResults = async ({
export const loadShoutsSearch = async ({
layout,
query,
limit,
@ -76,25 +76,21 @@ export const loadSearchResults = async ({
limit?: number
offset?: number
}): Promise<void> => {
const newLayoutShouts = await apiClient.getSearchResults({ layout, query, limit, offset })
const by = {
layout: layout,
query: query
}
const amount = limit
const newLayoutShouts = await apiClient.loadShoutsBy({ by, amount, offset })
addLayoutShouts(layout, newLayoutShouts)
}
type InitialState = {
sortedLayoutShouts?: Shout[]
topRatedLayoutShouts?: Shout[]
topRatedMonthLayoutShouts?: Shout[]
}
export const useLayoutsStore = (layout: LayoutType, initialData: Shout[]) => {
addLayoutShouts(layout, initialData || [])
return {
addLayoutShouts,
sortedLayoutShouts,
loadSearchResults,
loadRecentLayoutShouts,
loadTopMonthLayoutShouts,
loadTopLayoutShouts
loadShoutsSearch
}
}

View File

@ -6,6 +6,8 @@ import { reduceBy } from '../../utils/reduce'
// import { roomConnect } from '../../utils/p2p'
// FIXME
export const REACTIONS_AMOUNT_PER_PAGE = 100
let reactionsOrdered: WritableAtom<Reaction[]>
export const reactions = atom<{ [slug: string]: Reaction[] }>({}) // by shout
@ -19,14 +21,14 @@ export const useReactionsStore = (initial?: Reaction[]) => {
export const loadArticleReactions = async ({
articleSlug,
limit = 100,
limit = REACTIONS_AMOUNT_PER_PAGE,
offset = 0
}: {
articleSlug: string
limit?: number
offset?: number
}): Promise<void> => {
const data = await apiClient.getReactionsForShouts({ shoutSlugs: [articleSlug], limit, offset })
const data = await apiClient.loadReactionsBy({ by: { shout: articleSlug }, amount: limit, offset })
// TODO: const [data, provider] = roomConnect(articleSlug, username, "reactions")
reactionsOrdered.set(data)
}
@ -40,7 +42,11 @@ export const loadReactions = async ({
limit: number
offset: number
}): Promise<void> => {
const reactionsForShouts = await apiClient.getReactionsForShouts({ shoutSlugs, limit, offset })
const reactionsForShouts = await apiClient.loadReactionsBy({
by: { shouts: shoutSlugs },
amount: limit,
offset
})
reactionsOrdered.set(reactionsForShouts)
}

View File

@ -4,6 +4,7 @@ main {
flex-direction: column;
position: relative;
}
// TODO: добавлять когда открыт чат
body {
overflow: hidden;
@ -35,6 +36,7 @@ body {
position: relative;
}
}
// список диалогов и юзеров
.chat-list {
display: flex;
@ -43,6 +45,7 @@ body {
height: calc(100% - 10px);
$fade-height: 10px;
.holder {
overflow: hidden;
flex: 1;
@ -63,6 +66,7 @@ body {
top: 0;
background: linear-gradient(white, transparent $fade-height);
}
&::after {
bottom: 0;
background: linear-gradient(transparent, white $fade-height);
@ -93,7 +97,9 @@ body {
// табы выбора списка
.chat-list__types {
@include font-size(1.7rem);
margin: 16px 0;
ul {
display: flex;
flex-wrap: wrap;
@ -243,7 +249,6 @@ body {
.conversation__message {
font-size: 14px;
max-width: 60%;
border-radius: 16px;
padding: 12px 16px;

View File

@ -10,7 +10,6 @@ import type {
import { publicGraphQLClient } from '../graphql/publicGraphQLClient'
import { getToken, privateGraphQLClient } from '../graphql/privateGraphQLClient'
import topicsAll from '../graphql/query/topics-all'
import reactionsForShouts from '../graphql/query/reactions-load-by'
import mySession from '../graphql/mutation/my-session'
import authLogoutQuery from '../graphql/mutation/auth-logout'
import authLoginQuery from '../graphql/query/auth-login'
@ -25,7 +24,6 @@ import authorsAll from '../graphql/query/authors-all'
import reactionCreate from '../graphql/mutation/reaction-create'
import reactionDestroy from '../graphql/mutation/reaction-destroy'
import reactionUpdate from '../graphql/mutation/reaction-update'
import incrementView from '../graphql/mutation/increment-view'
import createArticle from '../graphql/mutation/article-create'
import myChats from '../graphql/query/chats-load'
import loadChat from '../graphql/query/chat-messages-load-by'
@ -35,6 +33,7 @@ import shoutsLoadBy from '../graphql/query/articles-load-by'
import reactionsLoadBy from '../graphql/query/reactions-load-by'
import authorsLoadBy from '../graphql/query/authors-load-by'
import createChatQuery from '../graphql/mutation/create-chat'
import { REACTIONS_AMOUNT_PER_PAGE } from '../stores/zine/reactions'
const FEED_SIZE = 50
@ -148,37 +147,6 @@ export const apiClient = {
return response.data.confirmEmail
},
getTopArticles: async () => {
const by = {
stat: 'rating',
visibility: 'public'
}
const response = await publicGraphQLClient.query(shoutsLoadBy, { by, limit: 10, offset: 0 }).toPromise()
return response.data.loadShoutsBy
},
getTopMonthArticles: async () => {
const by = {
stat: 'rating',
visibility: 'public',
days: 30
}
const response = await publicGraphQLClient.query(shoutsLoadBy, { by, limit: 10, offset: 0 }).toPromise()
return response.data.loadShoutsBy
},
getRecentPublishedArticles: async ({
limit = FEED_SIZE,
offset = 0
}: {
limit?: number
offset?: number
}) => {
const by = {
visibility: 'public'
}
const response = await publicGraphQLClient.query(shoutsLoadBy, { by, limit, offset }).toPromise()
return response.data.loadShoutsBy
},
getRandomTopics: async ({ amount }: { amount: number }) => {
const response = await publicGraphQLClient.query(topicsRandomQuery, { amount }).toPromise()
@ -188,100 +156,6 @@ export const apiClient = {
return response.data.topicsRandom
},
getSearchResults: async ({
query,
limit = FEED_SIZE,
offset = 0
}: {
query: string
limit: number
offset?: number
}): Promise<Shout[]> => {
const by = {
title: query,
body: query
}
const response = await publicGraphQLClient
.query(shoutsLoadBy, {
by,
limit,
offset
})
.toPromise()
return response.data?.searchQuery || []
},
getRecentArticles: async ({
limit = FEED_SIZE,
offset = 0
}: {
limit: number
offset?: number
}): Promise<Shout[]> => {
const response = await publicGraphQLClient
.query(shoutsLoadBy, {
by: {},
limit,
offset
})
.toPromise()
return response.data.recentAll
},
getArticlesForTopics: async ({
topicSlugs,
limit,
offset = 0
}: {
topicSlugs: string[]
limit: number
offset?: number
}): Promise<Shout[]> => {
const by = {
topics: topicSlugs,
visibility: 'public'
}
const response = await publicGraphQLClient
.query(shoutsLoadBy, {
by,
limit,
offset
})
.toPromise()
if (response.error) {
console.error('[api-client] getArticlesForTopics', response.error)
}
return response.data.shoutsByTopics
},
getArticlesForAuthors: async ({
authorSlugs,
limit,
offset = 0
}: {
authorSlugs: string[]
limit: number
offset?: number
}): Promise<Shout[]> => {
const by = {
authors: authorSlugs,
visibility: 'public'
}
const vars = {
by,
limit,
offset
}
// console.debug(vars)
const response = await publicGraphQLClient.query(shoutsLoadBy, vars).toPromise()
if (response.error) {
console.error('[api-client] getArticlesForAuthors', response.error)
}
return response.data.shoutsByAuthors
},
// subscribe
@ -312,18 +186,6 @@ export const apiClient = {
return response.data.refreshSession
},
getPublishedArticles: async ({ limit = FEED_SIZE, offset }: { limit?: number; offset?: number }) => {
const by = {
visibility: 'public'
}
const response = await publicGraphQLClient.query(shoutsLoadBy, { by, limit, offset }).toPromise()
if (response.error) {
console.error('[api-client] getPublishedArticles', response.error)
}
return response.data.recentPublished
},
getAllTopics: async () => {
const response = await publicGraphQLClient.query(topicsAll, {}).toPromise()
if (response.error) {
@ -346,34 +208,6 @@ export const apiClient = {
const response = await publicGraphQLClient.query(topicBySlug, { slug }).toPromise()
return response.data.getTopic
},
getArticle: async ({ slug }: { slug: string }): Promise<Shout> => {
const response = await publicGraphQLClient
.query(shoutsLoadBy, { by: { slug }, amount: 1, offset: 0 })
.toPromise()
return response.data?.getShoutBySlug
},
// reactions
getReactionsForShouts: async ({
shoutSlugs,
limit = FEED_SIZE,
offset = 0
}: {
shoutSlugs: string[]
limit?: number
offset?: number
}): Promise<Reaction[]> => {
const response = await publicGraphQLClient
.query(reactionsForShouts, {
shouts: shoutSlugs,
limit,
offset
})
.toPromise()
return response.data.reactionsForShouts
},
createArticle: async ({ article }: { article: ShoutInput }) => {
const response = await privateGraphQLClient.mutation(createArticle, { shout: article }).toPromise()
console.debug('createArticle response:', response)
@ -394,9 +228,6 @@ export const apiClient = {
return response.data.deleteReaction
},
incrementView: async ({ articleSlug }) => {
await privateGraphQLClient.mutation(incrementView, { shout: articleSlug })
},
createChat: async ({ title, members }) => {
return await privateGraphQLClient
.mutation(createChatQuery, { title: title, members: members })
@ -407,31 +238,17 @@ export const apiClient = {
const resp = await privateGraphQLClient.query(myChats, payload).toPromise()
return resp.data.myChats
},
getRecentLayoutShouts: async ({ layout = 'article', amount = 50, offset = 0 }) => {
const by = {
layout
}
const resp = await publicGraphQLClient.query(shoutsLoadBy, { by, amount, offset }).toPromise()
return resp.data.recentLayoutShouts
loadAuthorsBy: async ({ by, amount = 50, offset = 0 }) => {
const resp = await publicGraphQLClient.query(authorsLoadBy, { by, amount, offset }).toPromise()
return resp.data.loadShoutsBy
},
getTopLayoutShouts: async ({ layout = 'article', amount = 50, offset = 0 }) => {
const by = {
layout,
stat: 'rating',
order: 'rating'
}
loadShoutsBy: async ({ by, amount = 50, offset = 0 }) => {
const resp = await publicGraphQLClient.query(shoutsLoadBy, { by, amount, offset }).toPromise()
return resp.data.topLayoutShouts
return resp.data.loadShoutsBy
},
getTopMonthLayoutShouts: async ({ layout = 'article', amount = 50, offset = 0 }) => {
const by = {
layout,
stat: 'rating',
order: 'rating',
days: 30
}
const resp = await publicGraphQLClient.query(shoutsLoadBy, { amount, offset, layout }).toPromise()
return resp.data.topMonthLayoutShouts
loadReactionsBy: async ({ by, amount = REACTIONS_AMOUNT_PER_PAGE, offset = 0 }) => {
const resp = await publicGraphQLClient.query(reactionsLoadBy, { by, amount, offset }).toPromise()
return resp.data.loadReactionsBy
},
getChatMessages: async ({
chat,

View File

@ -1,6 +1,6 @@
export const isDev = import.meta.env.MODE === 'development'
export const apiBaseUrl = 'https://testapi.discours.io'
//export const apiBaseUrl = 'https://testapi.discours.io'
// export const apiBaseUrl = 'https://newapi.discours.io'
// testapi.discours.io
// export const apiBaseUrl = 'http://localhost:8080'
export const apiBaseUrl = 'http://localhost:8080'