diff --git a/src/components/App.tsx b/src/components/App.tsx index 06aa4241..0481610f 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -57,6 +57,11 @@ const pagesMap: Record> = { authors: AllAuthorsPage, author: AuthorPage, feed: FeedPage, + feedMy: FeedPage, + feedNotifications: FeedPage, + feedBookmarks: FeedPage, + feedCollaborations: FeedPage, + feedDiscussions: FeedPage, article: ArticlePage, search: SearchPage, discussionRules: DiscussionRulesPage, diff --git a/src/components/Feed/Sidebar/Sidebar.module.scss b/src/components/Feed/Sidebar/Sidebar.module.scss index 89f57bbe..c0c09805 100644 --- a/src/components/Feed/Sidebar/Sidebar.module.scss +++ b/src/components/Feed/Sidebar/Sidebar.module.scss @@ -16,8 +16,13 @@ text-overflow: ellipsis; } + .selected { + font-weight: 700; + } + .counter { @include font-size(1.2rem); + align-items: center; align-self: flex-start; background: #f6f6f6; diff --git a/src/components/Feed/Sidebar/Sidebar.tsx b/src/components/Feed/Sidebar/Sidebar.tsx index 1622c4cb..e93fc5bf 100644 --- a/src/components/Feed/Sidebar/Sidebar.tsx +++ b/src/components/Feed/Sidebar/Sidebar.tsx @@ -10,23 +10,18 @@ import { useLocalize } from '../../../context/localize' import styles from './Sidebar.module.scss' import { clsx } from 'clsx' import Userpic from '../../Author/Userpic' +import { getPagePath } from '@nanostores/router' +import { router, useRouter } from '../../../stores/router' type FeedSidebarProps = { authors: Author[] } -type ListItem = { - title: string - icon?: string - counter?: number - href?: string - isBold?: boolean -} - export const Sidebar = (props: FeedSidebarProps) => { const { t } = useLocalize() const { seen } = useSeenStore() const { session } = useSession() + const { page } = useRouter() const { authorEntities } = useAuthorsStore({ authors: props.authors }) const { articlesByTopic } = useArticlesStore() const { topicEntities } = useTopicsStore() @@ -40,68 +35,87 @@ export const Sidebar = (props: FeedSidebarProps) => { return Boolean(seen()[authorSlug]) } - const menuItems: ListItem[] = [ - { - icon: 'feed-all', - title: t('general feed') - }, - { - icon: 'feed-my', - title: t('My feed') - }, - { - icon: 'feed-collaborate', - title: t('Accomplices') - }, - { - icon: 'feed-discussion', - title: t('Discussions'), - counter: 4 - }, - { - icon: 'feed-drafts', - title: t('Drafts'), - counter: 14 - }, - { - icon: 'bookmark', - title: t('Bookmarks'), - counter: 6 - }, - { - icon: 'feed-notifications', - title: t('Notifications') - } - ] - return (
diff --git a/src/components/Nav/Header.tsx b/src/components/Nav/Header.tsx index 12333241..080e430f 100644 --- a/src/components/Nav/Header.tsx +++ b/src/components/Nav/Header.tsx @@ -25,11 +25,6 @@ type Props = { export const Header = (props: Props) => { const { t } = useLocalize() - const resources: { name: string; route: keyof typeof ROUTES }[] = [ - { name: t('zine'), route: 'home' }, - { name: t('feed'), route: 'feed' }, - { name: t('topics'), route: 'topics' } - ] // signals const [getIsScrollingBottom, setIsScrollingBottom] = createSignal(false) const [getIsScrolled, setIsScrolled] = createSignal(false) @@ -112,13 +107,15 @@ export const Header = (props: Props) => {
{props.title}
diff --git a/src/components/Views/Feed.module.scss b/src/components/Views/Feed.module.scss index e1ecedbf..e0789630 100644 --- a/src/components/Views/Feed.module.scss +++ b/src/components/Views/Feed.module.scss @@ -55,6 +55,7 @@ .topic { @include font-size(1.2rem); + background: transparentize(#2638d9, 0.95); display: inline-block; font-weight: bold; @@ -91,44 +92,39 @@ ul { @include font-size(1.4rem); + font-weight: bold; margin: 1rem 0 0; line-height: 1.3; li { - margin-bottom: 1.6rem; - padding-left: 3.4rem; + margin-bottom: 1.2rem; + padding-left: 2.6rem; position: relative; + &::before { + background: url(/icons/knowledge-base-bullet.svg) no-repeat; + content: ''; + height: 1.6rem; + left: 0; + position: absolute; + width: 2rem; + } + &:last-child { margin-bottom: 0; } } } - a:link { + a { border: none; - - &::before { - background: url(/public/icons/knowledge-base-bullet.svg) no-repeat; - content: ''; - height: 2.4rem; - left: 0; - position: absolute; - top: -0.1em; - width: 2.4rem; - } - - &:hover { - &:before { - background-image: url(/public/icons/knowledge-base-bullet-hover.svg); - } - } } } .comment { @include font-size(1.5rem); + line-height: 1.4; margin-bottom: 2.4rem; @@ -149,6 +145,7 @@ .commentBody { @include font-size(1.4rem); + margin-bottom: 1.2rem; line-clamp: 3; -webkit-line-clamp: 3; @@ -175,5 +172,6 @@ .commentArticleTitle, .commentAuthor { @include font-size(1.2rem); + font-weight: 500; } diff --git a/src/components/Views/Feed.tsx b/src/components/Views/Feed.tsx index 9bdde1a6..ca96001d 100644 --- a/src/components/Views/Feed.tsx +++ b/src/components/Views/Feed.tsx @@ -1,35 +1,52 @@ -import { createEffect, createSignal, For, onMount, Show } from 'solid-js' +import { createEffect, createSignal, For, on, onMount, Show } from 'solid-js' import { Icon } from '../_shared/Icon' import { ArticleCard } from '../Feed/ArticleCard' import { AuthorCard } from '../Author/AuthorCard' import { Sidebar } from '../Feed/Sidebar' -import { loadShouts, useArticlesStore } from '../../stores/zine/articles' +import { loadShouts, loadMyFeed, useArticlesStore, resetSortedArticles } from '../../stores/zine/articles' import { useAuthorsStore } from '../../stores/zine/authors' import { useTopicsStore } from '../../stores/zine/topics' import { useTopAuthorsStore } from '../../stores/zine/topAuthors' -import { useSession } from '../../context/session' import { clsx } from 'clsx' import { useReactions } from '../../context/reactions' -import type { Author, Reaction } from '../../graphql/types.gen' +import type { Author, LoadShoutsOptions, Reaction } from '../../graphql/types.gen' import { getPagePath } from '@nanostores/router' -import { router } from '../../stores/router' +import { router, useRouter } from '../../stores/router' import { useLocalize } from '../../context/localize' import styles from './Feed.module.scss' import stylesTopic from '../Feed/CardTopic.module.scss' import stylesBeside from '../../components/Feed/Beside.module.scss' import { CommentDate } from '../Article/CommentDate' +import { Loading } from '../_shared/Loading' export const FEED_PAGE_SIZE = 20 +type FeedSearchParams = { + by: 'publish_date' | 'rating' | 'last_comment' +} + +const getOrderBy = (by: FeedSearchParams['by']) => { + if (by === 'rating') { + return 'rating_stat' + } + + if (by === 'last_comment') { + return 'last_comment' + } + + return '' +} + export const FeedView = () => { const { t } = useLocalize() + const { page, searchParams } = useRouter() + const [isLoading, setIsLoading] = createSignal(false) // state const { sortedArticles } = useArticlesStore() const { sortedAuthors } = useAuthorsStore() const { topTopics } = useTopicsStore() const { topAuthors } = useTopAuthorsStore() - const { session, user } = useSession() const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) const [topComments, setTopComments] = createSignal([]) @@ -37,30 +54,43 @@ export const FeedView = () => { actions: { loadReactionsBy } } = useReactions() - // TODO: - // const collaborativeShouts = createMemo(() => - // sortedArticles().filter((shout) => shout.visibility === 'authors') - // ) + createEffect( + on( + () => page().route + searchParams().by, + () => { + resetSortedArticles() + loadMore() + } + ) + ) - // createEffect(async () => { - // if (collaborativeShouts()) { - // await loadReactionsBy({ by: { shouts: collaborativeShouts().map((shout) => shout.slug) }, limit: 5 }) - // } - // }) - - createEffect(async () => { - if (user()) { - // load recent editing shouts ( visibility = authors ) - await loadShouts({ filters: { author: user().slug, visibility: 'authors' }, limit: 15 }) - } - }) - - const loadMore = async () => { - const { hasMore, newShouts } = await loadShouts({ - filters: { visibility: 'community' }, + const loadFeedShouts = () => { + const options: LoadShoutsOptions = { limit: FEED_PAGE_SIZE, offset: sortedArticles().length + } + + const orderBy = getOrderBy(searchParams().by) + + if (orderBy) { + options.order_by = orderBy + } + + if (page().route === 'feedMy') { + return loadMyFeed(options) + } + + // default feed + return loadShouts({ + ...options, + filters: { visibility: 'community' } }) + } + + const loadMore = async () => { + setIsLoading(true) + const { hasMore, newShouts } = await loadFeedShouts() + setIsLoading(false) loadReactionsBy({ by: { @@ -72,8 +102,6 @@ export const FeedView = () => { } onMount(async () => { - // load recent shouts not only published ( visibility = community ) - await loadMore() // load 5 recent comments overall const comments = await loadReactionsBy({ by: { comment: true }, limit: 5 }) setTopComments(comments) @@ -89,62 +117,75 @@ export const FeedView = () => {
- 0}> - - {(article) => } - - -
-

{t('Popular authors')}

- - {t('All authors')} - - -
- -
    - - {(author) => ( -
  • - -
  • - )} + }> + 0}> + + {(article) => } -
- - {(article) => } - -
+
+

{t('Popular authors')}

+ + {t('All authors')} + + +
- -

- -

+
    + + {(author) => ( +
  • + +
  • + )} +
    +
+ + + {(article) => } + +
+ + +

+ +

+
diff --git a/src/graphql/query/my-feed.ts b/src/graphql/query/my-feed.ts index 42463b92..a4b0f3b8 100644 --- a/src/graphql/query/my-feed.ts +++ b/src/graphql/query/my-feed.ts @@ -12,15 +12,10 @@ export default gql` # community mainTopic topics { - # id + id title body slug - stat { - shouts - authors - followers - } } authors { id diff --git a/src/pages/feedMy.page.route.ts b/src/pages/feedMy.page.route.ts new file mode 100644 index 00000000..05bd7efc --- /dev/null +++ b/src/pages/feedMy.page.route.ts @@ -0,0 +1,4 @@ +import { ROUTES } from '../stores/router' +import { getServerRoute } from '../utils/getServerRoute' + +export default getServerRoute(ROUTES.feedMy) diff --git a/src/stores/router.ts b/src/stores/router.ts index 89d37c2b..2352aa11 100644 --- a/src/stores/router.ts +++ b/src/stores/router.ts @@ -16,6 +16,11 @@ export const ROUTES = { authors: '/authors', author: '/author/:slug', feed: '/feed', + feedMy: '/feed/my', + feedNotifications: '/feed/notifications', + feedDiscussions: '/feed/discussions', + feedBookmarks: '/feed/bookmarks', + feedCollaborations: '/feed/collaborations', search: '/search/:q?', article: '/:slug', dogma: '/about/dogma', diff --git a/src/stores/zine/articles.ts b/src/stores/zine/articles.ts index 3b9829c0..b333aa99 100644 --- a/src/stores/zine/articles.ts +++ b/src/stores/zine/articles.ts @@ -154,6 +154,26 @@ export const loadShouts = async ( return { hasMore, newShouts } } +export const loadMyFeed = async ( + options: LoadShoutsOptions +): Promise<{ hasMore: boolean; newShouts: Shout[] }> => { + const newShouts = await apiClient.getMyFeed({ + ...options, + limit: options.limit + 1 + }) + + const hasMore = newShouts.length === options.limit + 1 + + if (hasMore) { + newShouts.splice(-1) + } + + addArticles(newShouts) + addSortedArticles(newShouts) + + return { hasMore, newShouts } +} + export const resetSortedArticles = () => { setSortedArticles([]) } diff --git a/src/utils/apiClient.ts b/src/utils/apiClient.ts index f255149e..607f2374 100644 --- a/src/utils/apiClient.ts +++ b/src/utils/apiClient.ts @@ -48,6 +48,7 @@ import authorsLoadBy from '../graphql/query/authors-load-by' import shoutsLoadBy from '../graphql/query/articles-load-by' import draftsLoad from '../graphql/query/drafts-load' import shoutLoad from '../graphql/query/article-load' +import myFeed from '../graphql/query/my-feed' import loadRecipients from '../graphql/query/chat-recipients' import createMessage from '../graphql/mutation/create-chat-message' import updateProfile from '../graphql/mutation/update-profile' @@ -330,6 +331,16 @@ export const apiClient = { return resp.data.loadShouts }, + getMyFeed: async (options: LoadShoutsOptions) => { + const resp = await privateGraphQLClient.query(myFeed, { options }).toPromise() + + if (resp.error) { + console.error(resp) + } + + return resp.data.myFeed + }, + getReactionsBy: async ({ by, limit }: { by: ReactionBy; limit?: number }) => { const resp = await publicGraphQLClient .query(reactionsLoadBy, { by, limit: limit ?? 1000, offset: 0 })