Merge branch 'fix/topic-header' of https://github.com/Discours/discoursio-webapp into fix/topic-header

This commit is contained in:
kvakazyambra 2024-05-21 00:36:00 +03:00
commit be4d16b1a5
23 changed files with 619 additions and 195 deletions

View File

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.125 12.75H4.5C4.08854 12.75 3.75 12.4115 3.75 12C3.75 11.5885 4.08854 11.25 4.5 11.25H19.125C19.5365 11.25 19.875 11.5885 19.875 12C19.875 12.4115 19.5365 12.75 19.125 12.75Z" fill="currentColor"/>
<path
d="M14.0678 18.3593C13.8803 18.3593 13.6928 18.2916 13.547 18.151C13.2501 17.8593 13.2397 17.3853 13.5314 17.0885L18.4584 11.9999L13.5314 6.91137C13.2397 6.6145 13.2501 6.14054 13.547 5.84887C13.8439 5.56241 14.3178 5.57283 14.6043 5.8697L20.0366 11.4791C20.3178 11.7707 20.3178 12.2291 20.0366 12.5207L14.6043 18.1301C14.4584 18.2864 14.2657 18.3593 14.0678 18.3593Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 713 B

View File

@ -46,6 +46,7 @@
"Back": "Back",
"Be the first to rate": "Be the first to rate",
"Become an author": "Become an author",
"Block rules": "За что можно получить бан",
"Bold": "Bold",
"Bookmarked": "Saved",
"Bookmarks": "Bookmarks",
@ -86,6 +87,7 @@
"Commenting": "Commenting",
"Comments": "Comments",
"CommentsWithCount": "{count, plural, =0 {{count} comments} one {{count} comment} few {{count} comments} other {{count} comments}}",
"Common feed": "All",
"Communities": "Communities",
"Community Discussion Rules": "Community Discussion Rules",
"Community Principles": "Community Principles",
@ -107,9 +109,11 @@
"Create an account to subscribe": "Create an account to subscribe",
"Create an account to vote": "Create an account to vote",
"Create gallery": "Create gallery",
"Create own feed": "Создать свою ленту",
"Create post": "Create post",
"Create video": "Create video",
"Crop image": "Crop image",
"Current discussions": "Актуальные дискуссии",
"Culture": "Culture",
"Date of Birth": "Date of Birth",
"Decline": "Decline",
@ -155,6 +159,8 @@
"Feed": "Feed",
"Feedback": "Feedback",
"Fill email": "Fill email",
"Find co-authors": "Найти соавторов",
"Find collaborators": "Найдите соавторов и&nbsp;экспертов",
"Fixed": "Fixed",
"Follow the topic": "Follow the topic",
"Follow": "Follow",
@ -166,6 +172,7 @@
"Gallery": "Gallery",
"Get to know the most intelligent people of our time, edit and discuss the articles, share your expertise, rate and decide what to publish in the magazine": "Get to know the most intelligent people of our time, edit and discuss the articles, share your expertise, rate and decide what to publish in the magazine",
"Go to main page": "Go to main page",
"Go to discussions": "Перейти к обсуждениям",
"Group Chat": "Group Chat",
"Groups": "Groups",
"Header 1": "Header 1",
@ -213,9 +220,13 @@
"It does not look like url": "It doesn't look like a link",
"Italic": "Italic",
"Join our maillist": "To receive the best postings, just enter your email",
"Join our team of authors": "Join our team of authors",
"Join our team of authors text": "Каждый месяц на&nbsp;Дискурсе публикуются десятки новых авторов. Станьте одним из&nbsp;них&nbsp;— предложите свой материал в&nbsp;журнал и&nbsp;присоединитесь к&nbsp;горизонтальной редакции",
"Join the community": "Join the community",
"Join the global community of authors!": "Join the global community of authors from all over the world!",
"Join": "Join",
"Join discussions": "Присоединяйтесь к&nbsp;дискуссиям",
"Join discussions text": "Дискурс&nbsp;— свободная платформа для осмысленного общения.<br/>Здесь появятся ваши реплики, чтобы в&nbsp;любой момент вернуться к&nbsp;диалогу.",
"Just start typing...": "Just start typing...",
"Knowledge base": "Knowledge base",
"Language": "Language",
@ -261,6 +272,7 @@
"Our regular contributor": "Our regular contributor",
"Paragraphs": "Абзацев",
"Participate in the Discours: share information, join the editorial team": "Участвуйте в Дискурсе: делитесь информацией, присоединяйтесь к редакции",
"Participate in discussions": "Участвуйте в дискуссиях",
"Participating": "Participating",
"Participation": "Participation",
"Partners": "Partners",
@ -274,6 +286,9 @@
"Paste Embed code": "Paste Embed code",
"Personal": "Personal",
"Pin": "Pin",
"Placeholder feed": "Подпишитесь на&nbsp;любимые темы, авторов и&nbsp;сообщества&nbsp;— моментально узнавайте о&nbsp;новых публикациях и&nbsp;обсуждениях",
"Placeholder feedCollaborations": "На&nbsp;платформе можно писать материалы вместе. Здесь появятся публикации, в&nbsp;которые вы внесли вклад",
"Placeholder feedDiscussions": "Дискурс&nbsp;— свободная платформа для осмысленного общения. Здесь появятся все ваши реплики, чтобы в&nbsp;любой момент вернуться к&nbsp;диалогу",
"Platform Guide": "Platform Guide",
"Please check your email address": "Please check your email address",
"Please confirm your email to finish": "Confirm your email and the action will complete",

View File

@ -31,7 +31,7 @@
"All posts rating": "Рейтинг всех постов",
"All posts": "Все публикации",
"All topics": "Все темы",
"All": "Общая лента",
"All": "Все",
"Almost done! Check your email.": "Почти готово! Осталось подтвердить вашу почту.",
"Are you sure you want to delete this comment?": "Уверены, что хотите удалить этот комментарий?",
"Are you sure you want to delete this draft?": "Уверены, что хотите удалить этот черновик?",
@ -49,6 +49,7 @@
"Back": "Назад",
"Be the first to rate": "Оцените первым",
"Become an author": "Стать автором",
"Block rules": "За что можно получить бан",
"Bold": "Жирный",
"Bookmarked": "Сохранено",
"Bookmarks": "Закладки",
@ -90,6 +91,7 @@
"Commenting": "Комментирование",
"Comments": "Комментарии",
"CommentsWithCount": "{count, plural, =0 {{count} комментариев} one {{count} комментарий} few {{count} комментария} other {{count} комментариев}}",
"Common feed": "Общая лента",
"Communities": "Сообщества",
"Community Discussion Rules": "Правила дискуссий в сообществе",
"Community Principles": "Принципы сообщества",
@ -111,10 +113,12 @@
"Create an account to subscribe": "Создайте аккаунт, чтобы подписаться",
"Create an account to vote": "Создайте аккаунт, чтобы голосовать",
"Create gallery": "Создать галерею",
"Create own feed": "Создать свою ленту",
"Create post": "Создать публикацию",
"Create video": "Создать видео",
"Crop image": "Кадрировать изображение",
"Culture": "Культура",
"Current discussions": "Актуальные дискуссии",
"Date of Birth": "Дата рождения",
"Decline": "Отмена",
"Delete cover": "Удалить обложку",
@ -161,6 +165,8 @@
"Feed": "Лента",
"Feedback": "Обратная связь",
"Fill email": "Введите почту",
"Find co-authors": "Найти соавторов",
"Find collaborators": "Найдите соавторов и&nbsp;экспертов",
"Fixed": "Все поправлено",
"Follow the topic": "Подписаться на тему",
"Follow": "Подписаться",
@ -174,6 +180,7 @@
"Get notifications": "Получать уведомления",
"Get to know the most intelligent people of our time, edit and discuss the articles, share your expertise, rate and decide what to publish in the magazine": "Познакомитесь с выдающимися людьми нашего времени, участвуйте в редактировании и обсуждении статей, выступайте экспертом, оценивайте материалы других авторов со всего мира и определяйте, какие статьи будут опубликованы в журнале",
"Go to main page": "Перейти на главную",
"Go to discussions": "Перейти к обсуждениям",
"Group Chat": "Общий чат",
"Groups": "Группы",
"Header 1": "Заголовок 1",
@ -223,9 +230,13 @@
"It does not look like url": "Это не похоже на ссылку",
"Italic": "Курсив",
"Join our maillist": "Чтобы получать рассылку лучших публикаций, просто укажите свою почту",
"Join our team of authors": "Станьте автором",
"Join our team of authors text": "Каждый месяц на&nbsp;Дискурсе публикуются десятки новых авторов.<br/>Станьте одним из&nbsp;них&nbsp;— предложите свой материал в&nbsp;журнал и&nbsp;присоединитесь к&nbsp;горизонтальной редакции",
"Join the community": "Присоединиться к сообществу",
"Join the global community of authors!": "Присоединятесь к глобальному сообществу авторов со всего мира!",
"Join": "Присоединиться",
"Join discussions": "Присоединяйтесь к&nbsp;дискуссиям",
"Join discussions text": "Дискурс&nbsp;— свободная платформа для осмысленного общения.<br/>Здесь появятся ваши реплики, чтобы в&nbsp;любой момент вернуться к&nbsp;диалогу.",
"Just start typing...": "Просто начните печатать...",
"Karma": "Карма",
"Knowledge base": "База знаний",
@ -273,6 +284,7 @@
"Our regular contributor": "Наш постоянный автор",
"Paragraphs": "Абзацев",
"Participate in the Discours: share information, join the editorial team": "Participate in the Discours: share information, join the editorial team",
"Participate in discussions": "Участвуйте в дискуссиях",
"Participating": "Участвовать",
"Participation": "Соучастие",
"Partners": "Партнёры",
@ -286,6 +298,9 @@
"Paste Embed code": "Вставьте embed код",
"Personal": "Личные",
"Pin": "Закрепить",
"Placeholder feed": "Подпишитесь на&nbsp;любимые темы, авторов и&nbsp;сообщества&nbsp;— моментально узнавайте о&nbsp;новых публикациях и&nbsp;обсуждениях",
"Placeholder feedCollaborations": "На&nbsp;платформе можно писать материалы вместе. Здесь появятся публикации, в&nbsp;которые вы внесли вклад",
"Placeholder feedDiscussions": "Дискурс&nbsp;— свободная платформа для осмысленного общения. Здесь появятся все ваши реплики, чтобы в&nbsp;любой момент вернуться к&nbsp;диалогу",
"Platform Guide": "Гид по дискурсу",
"Please check your email address": "Пожалуйста, проверьте введенный адрес почты",
"Please check your inbox! We have sent a password reset link.": "Пожалуйста, проверьте свою почту, мы отправили вам письмо со ссылкой для сброса пароля",

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

View File

@ -127,7 +127,12 @@ export const AuthorCard = (props: Props) => {
<div class={styles.authorAbout} innerHTML={props.author.bio} />
</Show>
<Show when={props.followers?.length > 0 || props.following?.length > 0}>
<Subscribers followers={props.followers} following={props.following} />
<Subscribers
followers={props.followers}
followersAmount={props.author?.stat?.followers}
following={props.following}
followingAmount={props.author?.stat?.authors}
/>
</Show>
</div>
<ShowOnlyOnClient>

View File

@ -0,0 +1,213 @@
.placeholder {
border-radius: 2.2rem;
display: flex;
@include font-size(1.4rem);
font-weight: 500;
overflow: hidden;
position: relative;
h3 {
@include font-size(2.4rem);
}
button,
.button {
align-items: center;
border-radius: 1.2rem;
display: flex;
@include font-size(1.5rem);
gap: 0.6rem;
margin-top: 3rem;
padding: 1rem 2rem;
width: 100%;
.icon {
height: 2.4rem;
width: 2.4rem;
}
}
}
.placeholder--feed-mode {
aspect-ratio: 1 / 0.8;
flex-direction: column;
text-align: center;
&:after {
bottom: 0;
content: '';
height: 20%;
left: 0;
position: absolute;
width: 100%;
.placeholder--feed & {
background: linear-gradient(to top, #171032, rgba(23, 16, 50, 0));
}
.placeholder--feedCollaborations & {
background: linear-gradient(to top, #070709, rgba(7, 7, 9, 0));
}
}
.placeholderCover {
flex: 0 100%;
width: 100%;
img {
position: absolute;
}
}
}
.placeholder--profile-mode {
min-height: 28rem;
.placeholderCover {
flex: 0 45rem;
min-width: 45rem;
order: 2;
padding: 1.6rem;
img {
height: auto;
width: 100%;
}
}
.placeholderContent {
display: flex;
flex-direction: column;
justify-content: space-between;
@include font-size(2rem);
line-height: 1.2;
padding: 3rem;
}
h3 {
@include font-size(3.8rem);
}
.button {
background: var(--background-color-invert);
color: var(--default-color-invert);
bottom: 2rem;
position: absolute;
right: 2rem;
width: auto;
.icon {
filter: invert(1);
}
}
}
.placeholderCover {
position: relative;
img {
left: 0;
height: 100%;
object-fit: cover;
width: 100%;
}
}
.placeholderContent {
padding: 1.6rem;
}
.placeholder--feed,
.placeholder--feedCollaborations {
color: var(--default-color-invert);
button,
.button {
background: var(--background-color);
color: var(--default-color);
}
}
.placeholder--feed {
background: #171032;
.placeholderCover {
img {
object-position: top;
}
}
}
.placeholder--feedCollaborations {
background: #070709;
.placeholderCover {
img {
object-position: bottom;
}
}
}
.placeholder--feedDiscussions {
background: #E9E9EE;
.placeholderCover {
padding: 2rem;
text-align: center;
img {
height: 90%;
mix-blend-mode: multiply;
object-fit: contain;
top: 10%;
}
}
button,
.button {
background: var(--background-color-invert);
color: var(--default-color-invert);
}
}
.placeholder--author {
background: #E58B72;
}
.placeholder--authorComments {
background: #E9E9EE;
.placeholderCover {
img {
mix-blend-mode: multiply;
}
}
}
.bottomLinks {
display: flex;
@include font-size(1.6rem);
gap: 4rem;
a {
border: none !important;
padding-left: 2.6rem;
position: relative;
&:hover {
.icon {
filter: invert(0);
}
}
}
.icon {
filter: invert(1);
height: 1.8rem;
left: 0;
position: absolute;
transition: filter 0.2s;
width: 1.8rem;
}
}

View File

@ -0,0 +1,120 @@
import { clsx } from 'clsx'
import { For, Show } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session'
import { Icon } from '../../_shared/Icon'
import styles from './Placeholder.module.scss'
export type PlaceholderProps = {
type: string
mode: 'feed' | 'profile'
}
export const Placeholder = (props: PlaceholderProps) => {
const { t } = useLocalize()
const { author } = useSession()
const data = {
feed: {
image: 'placeholder-feed.webp',
header: t('Feed settings'),
text: t('Placeholder feed'),
buttonLabel: author() ? t('Popular authors') : t('Create own feed'),
href: '/authors?by=followers',
},
feedCollaborations: {
image: 'placeholder-experts.webp',
header: t('Find collaborators'),
text: t('Placeholder feedCollaborations'),
buttonLabel: t('Find co-authors'),
href: '/authors?by=name',
},
feedDiscussions: {
image: 'placeholder-discussions.webp',
header: t('Participate in discussions'),
text: t('Placeholder feedDiscussions'),
buttonLabel: author() ? t('Current discussions') : t('Enter'),
href: '/feed?by=last_comment',
},
author: {
image: 'placeholder-join.webp',
header: t('Join our team of authors'),
text: t('Join our team of authors text'),
buttonLabel: t('Create post'),
href: '/create',
profileLinks: [
{
href: '/how-to-write-a-good-article',
label: t('How to write a good article'),
},
],
},
authorComments: {
image: 'placeholder-discussions.webp',
header: t('Join discussions'),
text: t('Placeholder feedDiscussions'),
buttonLabel: t('Go to discussions'),
href: '/feed?by=last_comment',
profileLinks: [
{
href: '/about/discussion-rules',
label: t('Discussion rules'),
},
{
href: '/about/discussion-rules#ban',
label: t('Block rules'),
},
],
},
}
return (
<div
class={clsx(
styles.placeholder,
styles[`placeholder--${props.type}`],
styles[`placeholder--${props.mode}-mode`],
)}
>
<div class={styles.placeholderCover}>
<img src={`/${data[props.type].image}`} />
</div>
<div class={styles.placeholderContent}>
<div>
<h3 innerHTML={data[props.type].header} />
<p innerHTML={data[props.type].text} />
</div>
<Show when={data[props.type].profileLinks}>
<div class={styles.bottomLinks}>
<For each={data[props.type].profileLinks}>
{(link) => (
<a href={link.href}>
<Icon name="link-white" class={styles.icon} />
{link.label}
</a>
)}
</For>
</div>
</Show>
<Show
when={author()}
fallback={
<a class={styles.button} href="?m=auth&mode=login">
{data[props.type].buttonLabel}
</a>
}
>
<a class={styles.button} href={data[props.type].href}>
{data[props.type].buttonLabel}
<Show when={props.mode === 'profile'}>
<Icon name="arrow-right-2" class={styles.icon} />
</Show>
</a>
</Show>
</div>
</div>
)
}

View File

@ -0,0 +1 @@
export { Placeholder } from './Placeholder'

View File

@ -83,32 +83,6 @@ export const Sidebar = () => {
</span>
</a>
</li>
<li>
<a
href={getPagePath(router, 'feedBookmarks')}
class={clsx({
[styles.selected]: page().route === 'feedBookmarks',
})}
>
<span class={styles.sidebarItemName}>
<Icon name="bookmark" class={styles.icon} />
{t('Bookmarks')}
</span>
</a>
</li>
<li>
<a
href={getPagePath(router, 'feedNotifications')}
class={clsx({
[styles.selected]: page().route === 'feedNotifications',
})}
>
<span class={styles.sidebarItemName}>
<Icon name="feed-notifications" class={styles.icon} />
{t('Notifications')}
</span>
</a>
</li>
</ul>
<Show when={subscriptions.authors.length > 0 || subscriptions.topics.length > 0}>

View File

@ -57,5 +57,5 @@
}
.topicDetailsIcon {
display: block;
}

View File

@ -1,4 +1,4 @@
import type { Topic } from '../../graphql/schema/core.gen'
import type { Author, Topic } from '../../graphql/schema/core.gen'
import { clsx } from 'clsx'
import { Show, createEffect, createSignal } from 'solid-js'
@ -15,6 +15,8 @@ import styles from './Full.module.scss'
type Props = {
topic: Topic
followers?: Author[]
authors?: Author[]
}
export const FullTopic = (props: Props) => {
@ -54,7 +56,12 @@ export const FullTopic = (props: Props) => {
</div>
</Show>
<Subscribers followers={props.topic?.followers} following={props.topic?.following} />
<Subscribers
followers={props.followers}
followersAmount={props.topic?.stat?.followers}
following={props.authors}
followingAmount={props.topic?.stat?.authors}
/>
</div>
<div class={clsx(styles.topicActions)}>
@ -69,7 +76,7 @@ export const FullTopic = (props: Props) => {
</a>
</div>
<Show when={props.topic?.pic}>
<img src={props.topic.pic} alt={props.topic?.title} />
<img src={props.topic?.pic} alt={props.topic?.title} />
</Show>
</div>
)

View File

@ -27,6 +27,7 @@ import { Loading } from '../../_shared/Loading'
import { MODALS, hideModal } from '../../../stores/ui'
import { byCreated } from '../../../utils/sortby'
import stylesArticle from '../../Article/Article.module.scss'
import { Placeholder } from '../../Feed/Placeholder'
import styles from './Author.module.scss'
type Props = {
@ -259,6 +260,10 @@ export const AuthorView = (props: Props) => {
</div>
</Match>
<Match when={getPage().route === 'authorComments'}>
<div class="wide-container">
<Placeholder type={getPage().route} mode="profile" />
</div>
<div class="wide-container">
<div class="row">
<div class="col-md-20 col-lg-18">
@ -279,46 +284,56 @@ export const AuthorView = (props: Props) => {
</div>
</Match>
<Match when={getPage().route === 'author'}>
<Show when={sortedArticles().length === 1}>
<Row1 article={sortedArticles()[0]} noauthor={true} nodate={true} />
<Show
when={session()?.user?.app_data?.profile?.slug === props.authorSlug && !sortedArticles().length}
>
<div class="wide-container">
<Placeholder type={getPage().route} mode="profile" />
</div>
</Show>
<Show when={sortedArticles().length === 2}>
<Row2 articles={sortedArticles()} isEqual={true} noauthor={true} nodate={true} />
</Show>
<Show when={sortedArticles().length > 0}>
<Show when={sortedArticles().length === 1}>
<Row1 article={sortedArticles()[0]} noauthor={true} nodate={true} />
</Show>
<Show when={sortedArticles().length === 3}>
<Row3 articles={sortedArticles()} noauthor={true} nodate={true} />
</Show>
<Show when={sortedArticles().length === 2}>
<Row2 articles={sortedArticles()} isEqual={true} noauthor={true} nodate={true} />
</Show>
<Show when={sortedArticles().length > 3}>
<Row1 article={sortedArticles()[0]} noauthor={true} nodate={true} />
<Row2 articles={sortedArticles().slice(1, 3)} isEqual={true} noauthor={true} />
<Row1 article={sortedArticles()[3]} noauthor={true} nodate={true} />
<Row2 articles={sortedArticles().slice(4, 6)} isEqual={true} noauthor={true} />
<Row1 article={sortedArticles()[6]} noauthor={true} nodate={true} />
<Row2 articles={sortedArticles().slice(7, 9)} isEqual={true} noauthor={true} />
<Show when={sortedArticles().length === 3}>
<Row3 articles={sortedArticles()} noauthor={true} nodate={true} />
</Show>
<For each={pages()}>
{(page) => (
<>
<Row1 article={page[0]} noauthor={true} nodate={true} />
<Row2 articles={page.slice(1, 3)} isEqual={true} noauthor={true} />
<Row1 article={page[3]} noauthor={true} nodate={true} />
<Row2 articles={page.slice(4, 6)} isEqual={true} noauthor={true} />
<Row1 article={page[6]} noauthor={true} nodate={true} />
<Row2 articles={page.slice(7, 9)} isEqual={true} noauthor={true} />
</>
)}
</For>
</Show>
<Show when={sortedArticles().length > 3}>
<Row1 article={sortedArticles()[0]} noauthor={true} nodate={true} />
<Row2 articles={sortedArticles().slice(1, 3)} isEqual={true} noauthor={true} />
<Row1 article={sortedArticles()[3]} noauthor={true} nodate={true} />
<Row2 articles={sortedArticles().slice(4, 6)} isEqual={true} noauthor={true} />
<Row1 article={sortedArticles()[6]} noauthor={true} nodate={true} />
<Row2 articles={sortedArticles().slice(7, 9)} isEqual={true} noauthor={true} />
<Show when={isLoadMoreButtonVisible()}>
<p class="load-more-container">
<button class="button" onClick={loadMore}>
{t('Load more')}
</button>
</p>
<For each={pages()}>
{(page) => (
<>
<Row1 article={page[0]} noauthor={true} nodate={true} />
<Row2 articles={page.slice(1, 3)} isEqual={true} noauthor={true} />
<Row1 article={page[3]} noauthor={true} nodate={true} />
<Row2 articles={page.slice(4, 6)} isEqual={true} noauthor={true} />
<Row1 article={page[6]} noauthor={true} nodate={true} />
<Row2 articles={page.slice(7, 9)} isEqual={true} noauthor={true} />
</>
)}
</For>
</Show>
<Show when={isLoadMoreButtonVisible()}>
<p class="load-more-container">
<button class="button" onClick={loadMore}>
{t('Load more')}
</button>
</p>
</Show>
</Show>
</Match>
</Switch>

View File

@ -175,6 +175,7 @@
-webkit-line-clamp: 1;
a {
border: none;
color: rgb(0 0 0 / 65%);
&:hover {

View File

@ -20,6 +20,7 @@ import { getShareUrl } from '../../Article/SharePopup'
import { AuthorBadge } from '../../Author/AuthorBadge'
import { AuthorLink } from '../../Author/AuthorLink'
import { ArticleCard } from '../../Feed/ArticleCard'
import { Placeholder } from '../../Feed/Placeholder'
import { Sidebar } from '../../Feed/Sidebar'
import { Modal } from '../../Nav/Modal'
import { DropDown } from '../../_shared/DropDown'
@ -100,7 +101,7 @@ export const FeedView = (props: Props) => {
const { page, searchParams, changeSearchParams } = useRouter<FeedSearchParams>()
const [isLoading, setIsLoading] = createSignal(false)
const [isRightColumnLoaded, setIsRightColumnLoaded] = createSignal(false)
const { session } = useSession()
const { author, session } = useSession()
const { loadReactionsBy } = useReactions()
const { sortedArticles } = useArticlesStore()
const { topTopics } = useTopics()
@ -234,107 +235,112 @@ export const FeedView = (props: Props) => {
</div>
<div class="col-md-12 offset-xl-1">
<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 === 'likes',
})}
>
<span class="link" onClick={() => changeSearchParams({ by: 'likes' })}>
{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>
<div class={styles.dropdowns}>
<Show when={searchParams().by && searchParams().by !== 'publish_date'}>
<Show
when={author() || !sortedArticles().length}
fallback={<Placeholder type={page().route} mode="feed" />}
>
<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 === 'likes',
})}
>
<span class="link" onClick={() => changeSearchParams({ by: 'likes' })}>
{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>
<div class={styles.dropdowns}>
<Show when={searchParams().by && searchParams().by !== 'publish_date'}>
<DropDown
popupProps={{ horizontalAnchor: 'right' }}
options={periods}
currentOption={currentPeriod()}
triggerCssClass={styles.periodSwitcher}
onChange={(period: PeriodItem) => changeSearchParams({ period: period.value })}
/>
</Show>
<DropDown
popupProps={{ horizontalAnchor: 'right' }}
options={periods}
currentOption={currentPeriod()}
options={visibilities}
currentOption={currentVisibility()}
triggerCssClass={styles.periodSwitcher}
onChange={(period: PeriodItem) => changeSearchParams({ period: period.value })}
onChange={(visibility: VisibilityItem) =>
changeSearchParams({ visibility: visibility.value })
}
/>
</Show>
<DropDown
popupProps={{ horizontalAnchor: 'right' }}
options={visibilities}
currentOption={currentVisibility()}
triggerCssClass={styles.periodSwitcher}
onChange={(visibility: VisibilityItem) =>
changeSearchParams({ visibility: visibility.value })
}
/>
</div>
</div>
</div>
<Show when={!isLoading()} fallback={<Loading />}>
<Show when={sortedArticles().length > 0}>
<For each={sortedArticles().slice(0, 4)}>
{(article) => (
<ArticleCard
onShare={(shared) => handleShare(shared)}
onInvite={() => showModal('inviteMembers')}
article={article}
settings={{ isFeedMode: true }}
desktopCoverSize="M"
/>
)}
</For>
<Show when={!isLoading()} fallback={<Loading />}>
<Show when={sortedArticles().length > 0}>
<For each={sortedArticles().slice(0, 4)}>
{(article) => (
<ArticleCard
onShare={(shared) => handleShare(shared)}
onInvite={() => showModal('inviteMembers')}
article={article}
settings={{ isFeedMode: true }}
desktopCoverSize="M"
/>
)}
</For>
<div class={styles.asideSection}>
<div class={stylesBeside.besideColumnTitle}>
<h4>{t('Popular authors')}</h4>
<a href="/authors">
{t('All authors')}
<Icon name="arrow-right" class={stylesBeside.icon} />
</a>
<div class={styles.asideSection}>
<div class={stylesBeside.besideColumnTitle}>
<h4>{t('Popular authors')}</h4>
<a href="/authors">
{t('All authors')}
<Icon name="arrow-right" class={stylesBeside.icon} />
</a>
</div>
<ul class={stylesBeside.besideColumn}>
<For each={topAuthors().slice(0, 5)}>
{(author) => (
<li>
<AuthorBadge author={author} />
</li>
)}
</For>
</ul>
</div>
<ul class={stylesBeside.besideColumn}>
<For each={topAuthors().slice(0, 5)}>
{(author) => (
<li>
<AuthorBadge author={author} />
</li>
)}
</For>
</ul>
</div>
<For each={sortedArticles().slice(4)}>
{(article) => (
<ArticleCard article={article} settings={{ isFeedMode: true }} desktopCoverSize="M" />
)}
</For>
</Show>
<For each={sortedArticles().slice(4)}>
{(article) => (
<ArticleCard article={article} settings={{ isFeedMode: true }} desktopCoverSize="M" />
)}
</For>
</Show>
<Show when={isLoadMoreButtonVisible()}>
<p class="load-more-container">
<button class="button" onClick={loadMore}>
{t('Load more')}
</button>
</p>
<Show when={isLoadMoreButtonVisible()}>
<p class="load-more-container">
<button class="button" onClick={loadMore}>
{t('Load more')}
</button>
</p>
</Show>
</Show>
</Show>
</div>

View File

@ -1,4 +1,11 @@
import { LoadShoutsOptions, Shout, Topic } from '../../graphql/schema/core.gen'
import {
Author,
AuthorsBy,
LoadShoutsOptions,
QueryLoad_Authors_ByArgs,
Shout,
Topic,
} from '../../graphql/schema/core.gen'
import { clsx } from 'clsx'
import { For, Show, createEffect, createMemo, createSignal, on, onMount } from 'solid-js'
@ -33,6 +40,7 @@ interface Props {
topic: Topic
shouts: Shout[]
topicSlug: string
followers?: Author[]
}
export const PRERENDERED_ARTICLES_COUNT = 28
@ -56,6 +64,11 @@ export const TopicView = (props: Props) => {
setTopic(topics[props.topicSlug])
}
})
const [followers, setFollowers] = createSignal<Author[]>(props.followers || [])
const loadTopicFollowers = async () => {
const result = await apiClient.getTopicFollowers({ slug: props.topicSlug })
setFollowers(result)
}
const loadFavoriteTopArticles = async (topic: string) => {
const options: LoadShoutsOptions = {
@ -81,6 +94,12 @@ export const TopicView = (props: Props) => {
setReactedTopMonthArticles(result)
}
const [topicAuthors, setTopicAuthors] = createSignal<Author[]>([])
const loadTopicAuthors = async () => {
const by: AuthorsBy = { topic: props.topicSlug }
const result = await apiClient.loadAuthorsBy({ by })
setTopicAuthors(result)
}
const loadRandom = () => {
loadFavoriteTopArticles(topic()?.slug)
@ -89,8 +108,12 @@ export const TopicView = (props: Props) => {
createEffect(
on(
() => topic(),
() => loadRandom(),
() => topic()?.id,
(_) => {
loadTopicFollowers()
loadTopicAuthors()
loadRandom()
},
{ defer: true },
),
)
@ -158,7 +181,7 @@ export const TopicView = (props: Props) => {
<Meta name="twitter:card" content="summary_large_image" />
<Meta name="twitter:title" content={title()} />
<Meta name="twitter:description" content={description()} />
<FullTopic topic={topic()} />
<FullTopic topic={topic()} followers={followers()} authors={topicAuthors()} />
<div class="wide-container">
<div class={clsx(styles.groupControls, 'row group__controls')}>
<div class="col-md-16">

View File

@ -8,8 +8,10 @@ import { Userpic } from '../../Author/Userpic'
import styles from './Subscribers.module.scss'
type Props = {
followers: Author[]
followers?: Author[]
followersAmount?: number
following?: Array<Author | Topic>
followingAmount?: number
}
export const Subscribers = (props: Props) => {
@ -17,21 +19,21 @@ export const Subscribers = (props: Props) => {
return (
<div class={styles.subscribersContainer}>
<Show when={props.followers && props.followers.length > 0}>
<a href="?m=followers" class={styles.subscribers}>
<a href="?m=followers" class={styles.subscribers}>
<Show when={props.followers && props.followers.length > 0}>
<For each={props.followers.slice(0, 3)}>
{(f) => <Userpic size={'XS'} name={f.name} userpic={f.pic} class={styles.subscribersItem} />}
</For>
<div class={styles.subscribersCounter}>
{t('SubscriberWithCount', {
count: props.followers.length ?? 0,
})}
</div>
</a>
</Show>
</Show>
<div class={styles.subscribersCounter}>
{t('SubscriberWithCount', {
count: props.followersAmount || props.followers.length || 0,
})}
</div>
</a>
<Show when={props.following && props.following.length > 0}>
<a href="?m=following" class={styles.subscribers}>
<a href="?m=following" class={styles.subscribers}>
<Show when={props.following && props.following.length > 0}>
<For each={props.following.slice(0, 3)}>
{(f) => {
if ('name' in f) {
@ -45,13 +47,13 @@ export const Subscribers = (props: Props) => {
return null
}}
</For>
<div class={styles.subscribersCounter}>
{t('SubscriptionWithCount', {
count: props?.following.length ?? 0,
})}
</div>
</a>
</Show>
</Show>
<div class={styles.subscribersCounter}>
{t('SubscriptionWithCount', {
count: props.followingAmount || props.following?.length || 0,
})}
</div>
</a>
</div>
)
}

View File

@ -5,6 +5,7 @@ import type {
LoadShoutsOptions,
MutationDelete_ShoutArgs,
ProfileInput,
QueryGet_Topic_FollowersArgs,
QueryLoad_Authors_ByArgs,
QueryLoad_Shouts_Random_TopArgs,
QueryLoad_Shouts_SearchArgs,
@ -44,6 +45,7 @@ import authorsAll from '../query/core/authors-all'
import authorsLoadBy from '../query/core/authors-load-by'
import reactionsLoadBy from '../query/core/reactions-load-by'
import topicBySlug from '../query/core/topic-by-slug'
import topicFollowers from '../query/core/topic-followers'
import topicsAll from '../query/core/topics-all'
import topicsRandomQuery from '../query/core/topics-random'
@ -129,6 +131,11 @@ export const apiClient = {
return response.data.get_author_followers
},
getTopicFollowers: async ({ slug }: QueryGet_Topic_FollowersArgs): Promise<Author[]> => {
const response = await publicGraphQLClient.query(topicFollowers, { slug }).toPromise()
return response.data.get_topic_followers
},
getAuthorFollows: async (params: {
slug?: string
author_id?: number

View File

@ -0,0 +1,25 @@
import { gql } from '@urql/core'
export default gql`
query TopicFollowersQuery($slug: String) {
get_topic_followers(slug: $slug) {
id
slug
name
bio
about
pic
# communities
links
created_at
last_seen
stat {
shouts
authors
followers
rating
comments
}
}
}
`

View File

@ -40,7 +40,7 @@ export const DiscussionRulesPage = () => {
людей рождается истина.
</p>
<h3>За&nbsp;что можно получить дырку в&nbsp;карме и&nbsp;выиграть бан в&nbsp;сообществе</h3>
<h3 id="ban">За&nbsp;что можно получить дырку в&nbsp;карме и&nbsp;выиграть бан в&nbsp;сообществе</h3>
<ol>
<li>
<p>

View File

@ -1,6 +1,5 @@
import { Match, Switch, createEffect, on, onCleanup } from 'solid-js'
import { createEffect, on, onCleanup } from 'solid-js'
import { AuthGuard } from '../components/AuthGuard'
import { Feed } from '../components/Views/Feed'
import { PageLayout } from '../components/_shared/PageLayout'
import { useLocalize } from '../context/localize'
@ -43,16 +42,7 @@ export const FeedPage = () => {
return (
<PageLayout title={t('Feed')}>
<ReactionsProvider>
<Switch fallback={<Feed loadShouts={handleFeedLoadShouts} />}>
<Match when={page().route === 'feed'}>
<Feed loadShouts={handleFeedLoadShouts} />
</Match>
<Match when={page().route === 'feedMy'}>
<AuthGuard>
<Feed loadShouts={handleMyFeedLoadShouts} />
</AuthGuard>
</Match>
</Switch>
<Feed loadShouts={page().route === 'feedMy' ? handleMyFeedLoadShouts : handleFeedLoadShouts} />
</ReactionsProvider>
</PageLayout>
)