Feature/entity card (#246)

Add Bages components
This commit is contained in:
Ilya Y 2023-10-04 22:04:09 +03:00 committed by GitHub
parent 0f54752111
commit fac6422f2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 423 additions and 169 deletions

View File

@ -9,11 +9,9 @@
"Add audio": "Add audio", "Add audio": "Add audio",
"Add blockquote": "Add blockquote", "Add blockquote": "Add blockquote",
"Add comment": "Comment", "Add comment": "Comment",
"Here you can manage all your Discourse subscriptions": "Here you can manage all your Discourse subscriptions",
"Add cover": "Add cover", "Add cover": "Add cover",
"Add image": "Add image", "Add image": "Add image",
"Add images": "Add images", "Add images": "Add images",
"Collections": "Collections",
"Add intro": "Add intro", "Add intro": "Add intro",
"Add link": "Add link", "Add link": "Add link",
"Add rule": "Add rule", "Add rule": "Add rule",
@ -65,6 +63,7 @@
"Choose a title image for the article. You can immediately see how the publication card will look like.": "Choose a title image for the article. You can immediately see how the publication card will look like.", "Choose a title image for the article. You can immediately see how the publication card will look like.": "Choose a title image for the article. You can immediately see how the publication card will look like.",
"Choose who you want to write to": "Choose who you want to write to", "Choose who you want to write to": "Choose who you want to write to",
"Collaborate": "Help Edit", "Collaborate": "Help Edit",
"Collections": "Collections",
"Come up with a subtitle for your story": "Come up with a subtitle for your story", "Come up with a subtitle for your story": "Come up with a subtitle for your story",
"Come up with a title for your story": "Come up with a title for your story", "Come up with a title for your story": "Come up with a title for your story",
"Comment successfully deleted": "Comment successfully deleted", "Comment successfully deleted": "Comment successfully deleted",
@ -147,6 +146,7 @@
"Help": "Помощь", "Help": "Помощь",
"Help to edit": "Help to edit", "Help to edit": "Help to edit",
"Here you can customize your profile the way you want.": "Here you can customize your profile the way you want.", "Here you can customize your profile the way you want.": "Here you can customize your profile the way you want.",
"Here you can manage all your Discourse subscriptions": "Here you can manage all your Discourse subscriptions",
"Hide table of contents": "Hide table of contents", "Hide table of contents": "Hide table of contents",
"Highlight": "Highlight", "Highlight": "Highlight",
"Hooray! Welcome!": "Hooray! Welcome!", "Hooray! Welcome!": "Hooray! Welcome!",
@ -154,8 +154,8 @@
"Hot topics": "Hot topics", "Hot topics": "Hot topics",
"Hotkeys": "Горячие клавиши", "Hotkeys": "Горячие клавиши",
"How can I help/skills": "How can I help/skills", "How can I help/skills": "How can I help/skills",
"How to help": "How to help?",
"How it works": "How it works", "How it works": "How it works",
"How to help": "How to help?",
"How to write a good article": "Как написать хорошую статью", "How to write a good article": "Как написать хорошую статью",
"How to write an article": "How to write an article", "How to write an article": "How to write an article",
"I have an account": "I have an account!", "I have an account": "I have an account!",
@ -202,16 +202,17 @@
"My feed": "My feed", "My feed": "My feed",
"My subscriptions": "Subscriptions", "My subscriptions": "Subscriptions",
"Name": "Name", "Name": "Name",
"Newsletter": "Newsletter",
"New literary work": "New literary work", "New literary work": "New literary work",
"New only": "New only", "New only": "New only",
"New password": "New password", "New password": "New password",
"New stories every day and even more!": "New stories and more are waiting for you every day!", "New stories every day and even more!": "New stories and more are waiting for you every day!",
"Newsletter": "Newsletter",
"Night mode": "Night mode", "Night mode": "Night mode",
"No such account, please try to register": "No such account found, please try to register", "No such account, please try to register": "No such account found, please try to register",
"Nothing here yet": "There's nothing here yet", "Nothing here yet": "There's nothing here yet",
"Nothing is here": "There is nothing here", "Nothing is here": "There is nothing here",
"Notifications": "Notifications", "Notifications": "Notifications",
"Registered since {{date}}": "Registered since {{date}}",
"Or continue with social network": "Or continue with social network", "Or continue with social network": "Or continue with social network",
"Or paste a link to an image": "Or paste a link to an image", "Or paste a link to an image": "Or paste a link to an image",
"Ordered list": "Ordered list", "Ordered list": "Ordered list",
@ -285,9 +286,9 @@
"Start conversation": "Start a conversation", "Start conversation": "Start a conversation",
"Subsccriptions": "Subscriptions", "Subsccriptions": "Subscriptions",
"Subscribe": "Subscribe", "Subscribe": "Subscribe",
"Subscribe us": "Subscribe us",
"Subscribe what you like to tune your personal feed": "Subscribe to topics that interest you to customize your personal feed and get instant updates on new posts and discussions", "Subscribe what you like to tune your personal feed": "Subscribe to topics that interest you to customize your personal feed and get instant updates on new posts and discussions",
"Subscribe who you like to tune your personal feed": "Subscribe to authors you're interested in to customize your personal feed and get instant updates on new posts and discussions", "Subscribe who you like to tune your personal feed": "Subscribe to authors you're interested in to customize your personal feed and get instant updates on new posts and discussions",
"Subscribe us": "Subscribe us",
"Subscription": "Subscription", "Subscription": "Subscription",
"Subscriptions": "Subscriptions", "Subscriptions": "Subscriptions",
"Substrate": "Substrate", "Substrate": "Substrate",
@ -328,6 +329,7 @@
"Upload video": "Upload video", "Upload video": "Upload video",
"Username": "Username", "Username": "Username",
"Userpic": "Userpic", "Userpic": "Userpic",
"Users": "Users",
"Video": "Video", "Video": "Video",
"Video format not supported": "Video format not supported", "Video format not supported": "Video format not supported",
"Views": "Views", "Views": "Views",
@ -351,8 +353,8 @@
"You ll be able to participate in discussions, rate others' comments and learn about new responses": "You ll be able to participate in discussions, rate others' comments and learn about new responses", "You ll be able to participate in discussions, rate others' comments and learn about new responses": "You ll be able to participate in discussions, rate others' comments and learn about new responses",
"You've confirmed email": "You've confirmed email", "You've confirmed email": "You've confirmed email",
"You've reached a non-existed page": "You've reached a non-existed page", "You've reached a non-existed page": "You've reached a non-existed page",
"Your name will appear on your profile page and as your signature in publications, comments and responses.": "Your name will appear on your profile page and as your signature in publications, comments and responses",
"Your email": "Your email", "Your email": "Your email",
"Your name will appear on your profile page and as your signature in publications, comments and responses.": "Your name will appear on your profile page and as your signature in publications, comments and responses",
"accomplices": "accomplices", "accomplices": "accomplices",
"actions": "actions", "actions": "actions",
"add link": "add link", "add link": "add link",
@ -395,17 +397,16 @@
"shout": "post", "shout": "post",
"sign up or sign in": "sign up or sign in", "sign up or sign in": "sign up or sign in",
"slug is used by another user": "Slug is already taken by another user", "slug is used by another user": "Slug is already taken by another user",
"terms of use": "terms of use",
"topics": "topics",
"user already exist": "user already exists",
"video": "video",
"view": "view",
"zine": "zine",
"subscriber": "subscriber", "subscriber": "subscriber",
"subscriber_rp": "subscriber", "subscriber_rp": "subscriber",
"subscribers": "subscribers", "subscribers": "subscribers",
"subscription": "subscription", "subscription": "subscription",
"subscription_rp": "subscription", "subscription_rp": "subscription",
"subscriptions": "subscriptions", "subscriptions": "subscriptions",
"Users": "Users" "terms of use": "terms of use",
"topics": "topics",
"user already exist": "user already exists",
"video": "video",
"view": "view",
"zine": "zine"
} }

View File

@ -67,6 +67,7 @@
"Choose a title image for the article. You can immediately see how the publication card will look like.": "Выберите заглавное изображение для статьи. Тут же сразу можно увидеть как будет выглядеть карточка публикации.", "Choose a title image for the article. You can immediately see how the publication card will look like.": "Выберите заглавное изображение для статьи. Тут же сразу можно увидеть как будет выглядеть карточка публикации.",
"Choose who you want to write to": "Выберите кому хотите написать", "Choose who you want to write to": "Выберите кому хотите написать",
"Collaborate": "Помочь редактировать", "Collaborate": "Помочь редактировать",
"Collections": "Коллекции",
"Come up with a subtitle for your story": "Придумайте подзаголовок вашей истории", "Come up with a subtitle for your story": "Придумайте подзаголовок вашей истории",
"Come up with a title for your story": "Придумайте заголовок вашей истории", "Come up with a title for your story": "Придумайте заголовок вашей истории",
"Comment successfully deleted": "Комментарий успешно удален", "Comment successfully deleted": "Комментарий успешно удален",
@ -75,7 +76,6 @@
"Confirm": "Подтвердить", "Confirm": "Подтвердить",
"Cooperate": "Соучаствовать", "Cooperate": "Соучаствовать",
"Copy": "Скопировать", "Copy": "Скопировать",
"Collections": "Коллекции",
"Copy link": "Скопировать ссылку", "Copy link": "Скопировать ссылку",
"Corrections history": "История правок", "Corrections history": "История правок",
"Create Chat": "Создать чат", "Create Chat": "Создать чат",
@ -111,6 +111,7 @@
"Email": "Почта", "Email": "Почта",
"Enter": "Войти", "Enter": "Войти",
"Enter URL address": "Введите адрес ссылки", "Enter URL address": "Введите адрес ссылки",
"Enter footnote text": "Введите текст сноски",
"Enter image description": "Введите описание изображения", "Enter image description": "Введите описание изображения",
"Enter image title": "Введите название изображения", "Enter image title": "Введите название изображения",
"Enter text": "Введите текст", "Enter text": "Введите текст",
@ -153,6 +154,7 @@
"Help": "Помощь", "Help": "Помощь",
"Help to edit": "Помочь редактировать", "Help to edit": "Помочь редактировать",
"Here you can customize your profile the way you want.": "Здесь можно настроить свой профиль так, как вы хотите.", "Here you can customize your profile the way you want.": "Здесь можно настроить свой профиль так, как вы хотите.",
"Here you can manage all your Discourse subscriptions": "Здесь можно управлять всеми своими подписками на Дискурсе",
"Hide table of contents": "Скрыть главление", "Hide table of contents": "Скрыть главление",
"Highlight": "Подсветка", "Highlight": "Подсветка",
"Hooray! Welcome!": "Ура! Добро пожаловать!", "Hooray! Welcome!": "Ура! Добро пожаловать!",
@ -209,18 +211,18 @@
"Move up": "Переместить вверх", "Move up": "Переместить вверх",
"My feed": "Моя лента", "My feed": "Моя лента",
"My subscriptions": "Подписки", "My subscriptions": "Подписки",
"Here you can manage all your Discourse subscriptions": "Здесь можно управлять всеми своими подписками на Дискурсе",
"Name": "Имя", "Name": "Имя",
"Newsletter": "Рассылка",
"New literary work": "Новое произведение", "New literary work": "Новое произведение",
"New only": "Только новые", "New only": "Только новые",
"New password": "Новый пароль", "New password": "Новый пароль",
"New stories every day and even more!": "Каждый день вас ждут новые истории и ещё много всего интересного!", "New stories every day and even more!": "Каждый день вас ждут новые истории и ещё много всего интересного!",
"Newsletter": "Рассылка",
"Night mode": "Ночная тема", "Night mode": "Ночная тема",
"No such account, please try to register": "Такой адрес не найден, попробуйте зарегистрироваться", "No such account, please try to register": "Такой адрес не найден, попробуйте зарегистрироваться",
"Nothing here yet": "Здесь пока ничего нет", "Nothing here yet": "Здесь пока ничего нет",
"Nothing is here": "Здесь ничего нет", "Nothing is here": "Здесь ничего нет",
"Notifications": "Уведомления", "Notifications": "Уведомления",
"Registered since {{date}}": "На сайте c {{date}}",
"Or continue with social network": "Или войдите через соцсеть", "Or continue with social network": "Или войдите через соцсеть",
"Or paste a link to an image": "Или вставьте ссылку на изображение", "Or paste a link to an image": "Или вставьте ссылку на изображение",
"Ordered list": "Нумерованный список", "Ordered list": "Нумерованный список",
@ -286,8 +288,8 @@
"Share": "Поделиться", "Share": "Поделиться",
"Short opening": "Расскажите вашу историю...", "Short opening": "Расскажите вашу историю...",
"Show": "Показать", "Show": "Показать",
"Show more": "Читать дальше",
"Show lyrics": "Текст песни", "Show lyrics": "Текст песни",
"Show more": "Читать дальше",
"Show table of contents": "Показать главление", "Show table of contents": "Показать главление",
"Slug": "Постоянная ссылка", "Slug": "Постоянная ссылка",
"Social networks": "Социальные сети", "Social networks": "Социальные сети",
@ -302,11 +304,10 @@
"Subheader": "Подзаголовок", "Subheader": "Подзаголовок",
"Subscribe": "Подписаться", "Subscribe": "Подписаться",
"Subscribe to comments": "Подписаться на комментарии", "Subscribe to comments": "Подписаться на комментарии",
"Subscribe us": "Подпишитесь на нас",
"Subscribe what you like to tune your personal feed": "Подпишитесь на интересующие вас темы, чтобы настроить вашу персональную ленту и моментально узнавать о новых публикациях и обсуждениях", "Subscribe what you like to tune your personal feed": "Подпишитесь на интересующие вас темы, чтобы настроить вашу персональную ленту и моментально узнавать о новых публикациях и обсуждениях",
"Subscribe who you like to tune your personal feed": "Подпишитесь на интересующих вас авторов, чтобы настроить вашу персональную ленту и моментально узнавать о новых публикациях и обсуждениях", "Subscribe who you like to tune your personal feed": "Подпишитесь на интересующих вас авторов, чтобы настроить вашу персональную ленту и моментально узнавать о новых публикациях и обсуждениях",
"Subscribe us": "Подпишитесь на нас",
"Subscription": "Подписка", "Subscription": "Подписка",
"subscription": "подписка",
"Subscriptions": "Подписки", "Subscriptions": "Подписки",
"Substrate": "Подложка", "Substrate": "Подложка",
"Success": "Успешно", "Success": "Успешно",
@ -340,12 +341,13 @@
"Try to find another way": "Попробуйте найти по-другому", "Try to find another way": "Попробуйте найти по-другому",
"Unfollow": "Отписаться", "Unfollow": "Отписаться",
"Unfollow the topic": "Отписаться от темы", "Unfollow the topic": "Отписаться от темы",
"Unnamed draft": "Unnamed draft", "Unnamed draft": "Черновик без названия",
"Upload": "Загрузить", "Upload": "Загрузить",
"Upload error": "Ошибка загрузки", "Upload error": "Ошибка загрузки",
"Upload video": "Загрузить видео", "Upload video": "Загрузить видео",
"Username": "Имя пользователя", "Username": "Имя пользователя",
"Userpic": "Аватар", "Userpic": "Аватар",
"Users": "Пользователи",
"Video": "Видео", "Video": "Видео",
"Video format not supported": "Тип видео не поддерживается", "Video format not supported": "Тип видео не поддерживается",
"Views": "Просмотры", "Views": "Просмотры",
@ -371,8 +373,8 @@
"You've confirmed email": "Вы подтвердили почту", "You've confirmed email": "Вы подтвердили почту",
"You've reached a non-existed page": "Вы попали на несуществующую страницу", "You've reached a non-existed page": "Вы попали на несуществующую страницу",
"You've successfully logged out": "Вы успешно вышли из аккаунта", "You've successfully logged out": "Вы успешно вышли из аккаунта",
"Your name will appear on your profile page and as your signature in publications, comments and responses.": "Ваше имя появится на странице вашего профиля и как ваша подпись в публикациях, комментариях и откликах",
"Your email": "Ваш email", "Your email": "Ваш email",
"Your name will appear on your profile page and as your signature in publications, comments and responses.": "Ваше имя появится на странице вашего профиля и как ваша подпись в публикациях, комментариях и откликах",
"actions": "действия", "actions": "действия",
"add link": "добавить ссылку", "add link": "добавить ссылку",
"all topics": "все темы", "all topics": "все темы",
@ -395,6 +397,7 @@
"email not confirmed": "email не подтвержден", "email not confirmed": "email не подтвержден",
"enter": "войдите", "enter": "войдите",
"feed": "лента", "feed": "лента",
"follower": "подписчик",
"general feed": "Общая лента", "general feed": "Общая лента",
"header 1": "заголовок 1", "header 1": "заголовок 1",
"header 2": "заголовок 2", "header 2": "заголовок 2",
@ -420,18 +423,16 @@
"sign up or sign in": "зарегистрироваться или войти", "sign up or sign in": "зарегистрироваться или войти",
"slug is used by another user": "Имя уже занято другим пользователем", "slug is used by another user": "Имя уже занято другим пользователем",
"squib": "Подверстка", "squib": "Подверстка",
"subscriber": "подписчик",
"subscriber_rp": "подписчика",
"subscribers": "подписчиков",
"subscription": "подписка",
"subscription_rp": "подписки",
"subscriptions": "подписок",
"terms of use": "правилами пользования сайтом", "terms of use": "правилами пользования сайтом",
"topics": "темы", "topics": "темы",
"user already exist": "пользователь уже существует", "user already exist": "пользователь уже существует",
"video": "видео", "video": "видео",
"view": "просмотр", "view": "просмотр",
"zine": "журнал", "zine": "журнал"
"Enter footnote text": "Введите текст сноски",
"follower": "подписчик",
"subscriber": "подписчик",
"subscriber_rp": "подписчика",
"subscribers": "подписчиков",
"subscription_rp": "подписки",
"subscriptions": "подписок",
"Users": "Пользователи"
} }

View File

@ -1,6 +1,6 @@
import { createEffect, JSX, Show } from 'solid-js' import { createEffect, JSX, Show } from 'solid-js'
import { useSession } from '../../context/session' import { useSession } from '../../context/session'
import { hideModal, showModal } from '../../stores/ui' import { hideModal } from '../../stores/ui'
import { useRouter } from '../../stores/router' import { useRouter } from '../../stores/router'
import { RootSearchParams } from '../../pages/types' import { RootSearchParams } from '../../pages/types'
import { AuthModalSearchParams } from '../Nav/AuthModal/types' import { AuthModalSearchParams } from '../Nav/AuthModal/types'

View File

@ -0,0 +1,32 @@
.AuthorBadge {
display: flex;
flex-flow: row nowrap;
align-items: flex-start;
margin-bottom: 1rem;
.info {
@include font-size(1.4rem);
display: flex;
flex-direction: column;
border: none;
&:hover {
background: unset;
}
.name {
color: var(--default-color);
font-weight: 500;
}
.bio {
color: var(--black-400);
}
}
.actions {
margin-left: auto;
padding-left: 1rem;
}
}

View File

@ -0,0 +1,92 @@
import { clsx } from 'clsx'
import styles from './AuthorBadge.module.scss'
import { Userpic } from '../Userpic'
import { Author, FollowingEntity } from '../../../graphql/types.gen'
import { createMemo, createSignal, Show } from 'solid-js'
import { formatDate } from '../../../utils'
import { useLocalize } from '../../../context/localize'
import { Button } from '../../_shared/Button'
import { useSession } from '../../../context/session'
import { follow, unfollow } from '../../../stores/zine/common'
import { CheckButton } from '../../_shared/CheckButton'
type Props = {
author: Author
minimizeSubscribeButton?: boolean
}
export const AuthorBadge = (props: Props) => {
const [isSubscribing, setIsSubscribing] = createSignal(false)
const {
isAuthenticated,
session,
actions: { loadSession }
} = useSession()
const { t } = useLocalize()
const subscribed = createMemo<boolean>(() => {
return session()?.news?.authors?.some((u) => u === props.author.slug) || false
})
const subscribe = async (really = true) => {
setIsSubscribing(true)
await (really
? follow({ what: FollowingEntity.Author, slug: props.author.slug })
: unfollow({ what: FollowingEntity.Author, slug: props.author.slug }))
await loadSession()
setIsSubscribing(false)
}
return (
<div class={clsx(styles.AuthorBadge)}>
<Userpic hasLink={true} isMedium={true} name={props.author.name} userpic={props.author.userpic} />
<a href={`/author/${props.author.slug}`} class={styles.info}>
<div class={styles.name}>{props.author.name}</div>
<Show
when={props.author.bio}
fallback={
<div class={styles.bio}>
{t('Registered since {{date}}', { date: formatDate(new Date(props.author.createdAt)) })}
</div>
}
>
<div class={clsx('text-truncate', styles.bio)}>{props.author.bio}</div>
</Show>
</a>
<Show when={isAuthenticated() && props.author.slug !== session().user.slug}>
<div class={styles.actions}>
<Show
when={!props.minimizeSubscribeButton}
fallback={
<CheckButton
text={t('Follow')}
checked={subscribed()}
onClick={() => subscribe(!subscribed)}
/>
}
>
<Show
when={subscribed()}
fallback={
<Button
variant="primary"
size="S"
value={isSubscribing() ? t('...subscribing') : t('Subscribe')}
onClick={() => subscribe(true)}
/>
}
>
<Button
onClick={() => subscribe(false)}
variant="secondary"
size="S"
value={t('You are subscribed')}
/>
</Show>
</Show>
</div>
</Show>
</div>
)
}

View File

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

View File

@ -1,6 +1,6 @@
.author { .author {
align-items: center;
display: flex; display: flex;
align-items: center;
flex-flow: row nowrap; flex-flow: row nowrap;
margin-bottom: 1.6rem; margin-bottom: 1.6rem;
@ -600,8 +600,10 @@
align-items: center; align-items: center;
margin-bottom: 0.4rem; margin-bottom: 0.4rem;
.authorName { .authorName,
.authorAbout {
@include font-size(1.2rem); @include font-size(1.2rem);
margin-bottom: 0; margin-bottom: 0;
} }

View File

@ -15,11 +15,11 @@ import { useLocalize } from '../../../context/localize'
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper' import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
import { Modal } from '../../Nav/Modal' import { Modal } from '../../Nav/Modal'
import { showModal } from '../../../stores/ui' import { showModal } from '../../../stores/ui'
import { TopicCard } from '../../Topic/Card'
import { getNumeralsDeclension } from '../../../utils/getNumeralsDeclension' import { getNumeralsDeclension } from '../../../utils/getNumeralsDeclension'
import { SubscriptionFilter } from '../../../pages/types' import { SubscriptionFilter } from '../../../pages/types'
import { isAuthor } from '../../../utils/isAuthor' import { isAuthor } from '../../../utils/isAuthor'
import { CheckButton } from '../../_shared/CheckButton' import { AuthorBadge } from '../AuthorBadge'
import { TopicBadge } from '../../Topic/TopicBadge'
type Props = { type Props = {
caption?: string caption?: string
@ -42,7 +42,6 @@ type Props = {
followers?: Author[] followers?: Author[]
following?: Array<Author | Topic> following?: Array<Author | Topic>
showPublicationsCounter?: boolean showPublicationsCounter?: boolean
minimizeSubscribeButton?: boolean
} }
export const AuthorCard = (props: Props) => { export const AuthorCard = (props: Props) => {
@ -285,35 +284,12 @@ export const AuthorCard = (props: Props) => {
</For> </For>
</div> </div>
</Show> </Show>
<Show when={!props.minimizeSubscribeButton}>
<Show <Show
when={subscribed()} when={subscribed()}
fallback={ fallback={
<button
onClick={handleSubscribe}
class={clsx('button', styles.button)}
classList={{
[styles.buttonSubscribe]: !props.isAuthorsList && !props.isTextButton,
'button--subscribe': !props.isAuthorsList,
'button--subscribe-topic': props.isAuthorsList || props.isTextButton,
[styles.buttonWrite]: props.isAuthorsList || props.isTextButton,
[styles.isSubscribing]: isSubscribing()
}}
disabled={isSubscribing()}
>
<Show when={!props.isAuthorsList && !props.isTextButton && !props.isAuthorPage}>
<Icon name="author-subscribe" class={styles.icon} />
</Show>
<Show when={props.isTextButton || props.isAuthorPage}>
<span class={clsx(styles.buttonLabel, styles.buttonLabelVisible)}>
{t('Follow')}
</span>
</Show>
</button>
}
>
<button <button
onClick={() => subscribe(false)} onClick={handleSubscribe}
class={clsx('button', styles.button)} class={clsx('button', styles.button)}
classList={{ classList={{
[styles.buttonSubscribe]: !props.isAuthorsList && !props.isTextButton, [styles.buttonSubscribe]: !props.isAuthorsList && !props.isTextButton,
@ -325,37 +301,52 @@ export const AuthorCard = (props: Props) => {
disabled={isSubscribing()} disabled={isSubscribing()}
> >
<Show when={!props.isAuthorsList && !props.isTextButton && !props.isAuthorPage}> <Show when={!props.isAuthorsList && !props.isTextButton && !props.isAuthorPage}>
<Icon name="author-unsubscribe" class={styles.icon} /> <Icon name="author-subscribe" class={styles.icon} />
</Show> </Show>
<Show when={props.isTextButton || props.isAuthorPage}> <Show when={props.isTextButton || props.isAuthorPage}>
<span <span class={clsx(styles.buttonLabel, styles.buttonLabelVisible)}>
class={clsx( {t('Follow')}
styles.buttonLabel,
styles.buttonLabelVisible,
styles.buttonUnfollowLabel
)}
>
{t('Unfollow')}
</span>
<span
class={clsx(
styles.buttonLabel,
styles.buttonLabelVisible,
styles.buttonSubscribedLabel
)}
>
{t('You are subscribed')}
</span> </span>
</Show> </Show>
</button> </button>
</Show> }
</Show> >
<Show when={props.minimizeSubscribeButton}> <button
<CheckButton
text={t('Follow')}
checked={subscribed()}
onClick={() => subscribe(false)} onClick={() => subscribe(false)}
/> class={clsx('button', styles.button)}
classList={{
[styles.buttonSubscribe]: !props.isAuthorsList && !props.isTextButton,
'button--subscribe': !props.isAuthorsList,
'button--subscribe-topic': props.isAuthorsList || props.isTextButton,
[styles.buttonWrite]: props.isAuthorsList || props.isTextButton,
[styles.isSubscribing]: isSubscribing()
}}
disabled={isSubscribing()}
>
<Show when={!props.isAuthorsList && !props.isTextButton && !props.isAuthorPage}>
<Icon name="author-unsubscribe" class={styles.icon} />
</Show>
<Show when={props.isTextButton || props.isAuthorPage}>
<span
class={clsx(
styles.buttonLabel,
styles.buttonLabelVisible,
styles.buttonUnfollowLabel
)}
>
{t('Unfollow')}
</span>
<span
class={clsx(
styles.buttonLabel,
styles.buttonLabelVisible,
styles.buttonSubscribedLabel
)}
>
{t('You are subscribed')}
</span>
</Show>
</button>
</Show> </Show>
<Show when={!props.hideWriteButton}> <Show when={!props.hideWriteButton}>
@ -390,17 +381,7 @@ export const AuthorCard = (props: Props) => {
<div class="row"> <div class="row">
<div class="col-24"> <div class="col-24">
<For each={props.followers}> <For each={props.followers}>
{(follower: Author) => ( {(follower: Author) => <AuthorBadge author={follower} />}
<AuthorCard
author={follower}
hideWriteButton={true}
hasLink={true}
isTextButton={true}
liteButtons={true}
truncateBio={true}
showPublicationsCounter={true}
/>
)}
</For> </For>
</div> </div>
</div> </div>
@ -444,23 +425,9 @@ export const AuthorCard = (props: Props) => {
<For each={following()}> <For each={following()}>
{(subscription) => {(subscription) =>
isAuthor(subscription) ? ( isAuthor(subscription) ? (
<AuthorCard <AuthorBadge author={subscription} />
author={subscription}
hideWriteButton={true}
hasLink={true}
isTextButton={true}
truncateBio={true}
showPublicationsCounter={true}
/>
) : ( ) : (
<TopicCard <TopicBadge topic={subscription} />
compact
isTopicInRow
showDescription
isCardMode
withIcon
topic={subscription}
/>
) )
} }
</For> </For>

View File

@ -3,14 +3,14 @@
background: #f7f7f8; background: #f7f7f8;
border-radius: 100%; border-radius: 100%;
display: flex; display: flex;
height: 32px;
justify-content: center; justify-content: center;
margin-right: 1.2rem; margin-right: 1.2rem;
min-width: 32px; min-width: 32px;
max-width: 32px; max-width: 32px;
height: 32px;
width: 32px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
width: 32px;
img { img {
height: 100%; height: 100%;
@ -55,6 +55,17 @@
} }
} }
&.medium {
width: 40px;
height: 40px;
min-width: 40px;
max-width: 40px;
.letters {
line-height: 40px;
}
}
&.big { &.big {
aspect-ratio: 1/1; aspect-ratio: 1/1;
margin: 0 auto; margin: 0 auto;

View File

@ -13,8 +13,8 @@ type Props = {
onClick?: () => void onClick?: () => void
loading?: boolean loading?: boolean
isBig?: boolean isBig?: boolean
isMedium?: boolean
hasLink?: boolean hasLink?: boolean
isAuthorsList?: boolean isAuthorsList?: boolean
isFeedMode?: boolean isFeedMode?: boolean
} }
@ -30,6 +30,7 @@ export const Userpic = (props: Props) => {
<div <div
class={clsx(styles.Userpic, props.class, { class={clsx(styles.Userpic, props.class, {
[styles.big]: props.isBig, [styles.big]: props.isBig,
[styles.medium]: props.isMedium,
[styles.authorsList]: props.isAuthorsList, [styles.authorsList]: props.isAuthorsList,
[styles.feedMode]: props.isFeedMode, [styles.feedMode]: props.isFeedMode,
['cursorPointer']: props.onClick ['cursorPointer']: props.onClick

View File

@ -167,7 +167,6 @@ export const ArticleCard = (props: ArticleCardProps) => {
</Show> </Show>
</a> </a>
</div> </div>
<Show when={!props.settings?.noauthor || !props.settings?.nodate}> <Show when={!props.settings?.noauthor || !props.settings?.nodate}>
<div <div
class={clsx(styles.shoutDetails, { [styles.shoutDetailsFeedMode]: props.settings?.isFeedMode })} class={clsx(styles.shoutDetails, { [styles.shoutDetailsFeedMode]: props.settings?.isFeedMode })}
@ -181,6 +180,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
author={author} author={author}
hideWriteButton={true} hideWriteButton={true}
hideFollow={true} hideFollow={true}
truncateBio={true}
isFeedMode={true} isFeedMode={true}
hasLink={!props.settings?.noAuthorLink} hasLink={!props.settings?.noAuthorLink}
/> />

View File

@ -2,16 +2,15 @@
import { For, Show } from 'solid-js' import { For, Show } from 'solid-js'
import { ArticleCard } from './ArticleCard' import { ArticleCard } from './ArticleCard'
import { AuthorCard } from '../Author/AuthorCard'
import { TopicCard } from '../Topic/Card' import { TopicCard } from '../Topic/Card'
import styles from './Beside.module.scss' import styles from './Beside.module.scss'
import type { Author, Shout, Topic, User } from '../../graphql/types.gen' import type { Author, Shout, Topic, User } from '../../graphql/types.gen'
import { Icon } from '../_shared/Icon' import { Icon } from '../_shared/Icon'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
import { AuthorBadge } from '../Author/AuthorBadge'
interface BesideProps { type Props = {
title?: string title?: string
values: (Shout | User | Topic | Author)[] values: (Shout | User | Topic | Author)[]
beside: Shout beside: Shout
@ -24,7 +23,7 @@ interface BesideProps {
nodate?: boolean nodate?: boolean
} }
export const Beside = (props: BesideProps) => { export const Beside = (props: Props) => {
const { t } = useLocalize() const { t } = useLocalize()
return ( return (
<Show when={!!props.beside?.slug && props.values?.length > 0}> <Show when={!!props.beside?.slug && props.values?.length > 0}>
@ -76,14 +75,7 @@ export const Beside = (props: BesideProps) => {
/> />
</Show> </Show>
<Show when={props.wrapper === 'author'}> <Show when={props.wrapper === 'author'}>
<AuthorCard <AuthorBadge author={value as Author} />
author={value as Author}
hideWriteButton={true}
hasLink={true}
truncateBio={true}
isTextButton={true}
class={styles.author}
/>
</Show> </Show>
<Show when={props.wrapper === 'article' && value?.slug}> <Show when={props.wrapper === 'article' && value?.slug}>
<ArticleCard <ArticleCard

View File

@ -46,7 +46,7 @@ export const Header = (props: Props) => {
actions: { requireAuthentication } actions: { requireAuthentication }
} = useSession() } = useSession()
const { page, searchParams } = useRouter<HeaderSearchParams>() const { searchParams } = useRouter<HeaderSearchParams>()
const [randomTopics, setRandomTopics] = createSignal([]) const [randomTopics, setRandomTopics] = createSignal([])
const [getIsScrollingBottom, setIsScrollingBottom] = createSignal(false) const [getIsScrollingBottom, setIsScrollingBottom] = createSignal(false)

View File

@ -0,0 +1,60 @@
.TopicBadge {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 1rem;
margin-bottom: 1rem;
.picture {
display: block;
width: 40px;
height: 40px;
min-width: 40px;
border-radius: 8px;
background-color: var(--black-50);
background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE5LjUgMTQuMjVDMTkuOTE0MSAxNC4yNSAyMC4yNSAxNC41ODU5IDIwLjI1IDE1QzIwLjI1IDE1LjQxNDEgMTkuOTE0MSAxNS43NSAxOS41IDE1Ljc1SDE1Ljc1VjE5LjVDMTUuNzUgMTkuOTE0MSAxNS40MTQxIDIwLjI1IDE1IDIwLjI1QzE0LjU4NTkgMjAuMjUgMTQuMjUgMTkuOTE0MSAxNC4yNSAxOS41VjE1Ljc1SDkuNzVWMTkuNUM5Ljc1IDE5LjkxNDEgOS40MTQwNiAyMC4yNSA5IDIwLjI1QzguNTg1OTQgMjAuMjUgOC4yNSAxOS45MTQxIDguMjUgMTkuNVYxNS43NUg0LjVDNC4wODU5NCAxNS43NSAzLjc1IDE1LjQxNDEgMy43NSAxNUMzLjc1IDE0LjU4NTkgNC4wODU5NCAxNC4yNSA0LjUgMTQuMjVIOC4yNVY5Ljc1SDQuNUM0LjA4NTk0IDkuNzUgMy43NSA5LjQxNDA2IDMuNzUgOUMzLjc1IDguNTg1OTQgNC4wODU5NCA4LjI1IDQuNSA4LjI1SDguMjVWNC41QzguMjUgNC4wODU5NCA4LjU4NTk0IDMuNzUgOSAzLjc1QzkuNDE0MDYgMy43NSA5Ljc1IDQuMDg1OTQgOS43NSA0LjVWOC4yNUgxNC4yNVY0LjVDMTQuMjUgNC4wODU5NCAxNC41ODU5IDMuNzUgMTUgMy43NUMxNS40MTQxIDMuNzUgMTUuNzUgNC4wODU5NCAxNS43NSA0LjVWOC4yNUgxOS41QzE5LjkxNDEgOC4yNSAyMC4yNSA4LjU4NTk0IDIwLjI1IDlDMjAuMjUgOS40MTQwNiAxOS45MTQxIDkuNzUgMTkuNSA5Ljc1SDE1Ljc1VjE0LjI1SDE5LjVaTTkuNzUgMTQuMjVIMTQuMjVWOS43NUg5Ljc1VjE0LjI1WiIgZmlsbD0iIzlGQTFBNyIvPgo8L3N2Zz4K');
background-position: 50% 50%;
background-repeat: no-repeat;
border: none;
&:hover {
background-color: var(--black-50);
}
&.withImage {
background-size: cover;
}
}
.info {
display: flex;
flex-direction: column;
border: none;
@include font-size(1.4rem);
&:hover {
background: unset;
}
.title {
color: var(--blue-500);
font-weight: 700;
}
.description {
color: var(--black-400);
}
.stat {
display: flex;
gap: 1rem;
flex-direction: row;
color: var(--default-color);
}
}
.actions {
margin-left: auto;
padding-left: 1rem;
}
}

View File

@ -0,0 +1,111 @@
import { clsx } from 'clsx'
import styles from './TopicBadge.module.scss'
import { FollowingEntity, Topic } from '../../../graphql/types.gen'
import { createMemo, createSignal, Show } from 'solid-js'
import { imageProxy } from '../../../utils/imageProxy'
import { capitalize } from '../../../utils'
import { Button } from '../../_shared/Button'
import { useSession } from '../../../context/session'
import { useLocalize } from '../../../context/localize'
import { follow, unfollow } from '../../../stores/zine/common'
import { CheckButton } from '../../_shared/CheckButton'
type Props = {
topic: Topic
minimizeSubscribeButton?: boolean
}
export const TopicBadge = (props: Props) => {
const [isSubscribing, setIsSubscribing] = createSignal(false)
const { t } = useLocalize()
const {
isAuthenticated,
session,
actions: { loadSession }
} = useSession()
const subscribed = createMemo(() => {
if (!session()?.user?.slug || !session()?.news?.topics) {
return false
}
return session()?.news.topics.includes(props.topic.slug)
})
const subscribe = async (really = true) => {
setIsSubscribing(true)
await (really
? follow({ what: FollowingEntity.Topic, slug: props.topic.slug })
: unfollow({ what: FollowingEntity.Topic, slug: props.topic.slug }))
await loadSession()
setIsSubscribing(false)
}
return (
<div class={styles.TopicBadge}>
<a
href={`/topic/${props.topic.slug}`}
class={clsx(styles.picture, { [styles.withImage]: props.topic.pic })}
style={props.topic.pic && { 'background-image': `url('${imageProxy(props.topic.pic)}')` }}
/>
<a href={`/topic/${props.topic.slug}`} class={styles.info}>
<span class={styles.title}>{capitalize(props.topic.title)}</span>
<Show when={props.topic.body}>
<div class={clsx('text-truncate', styles.description)}>{props.topic.body}</div>
</Show>
<span class={styles.stat}>
<Show when={props.topic.stat.authors}>
<div>
{t('Authors')}: {props.topic.stat.authors}
</div>
</Show>
<Show when={props.topic.stat.followers}>
<div>
{t('Followers')}: {props.topic.stat.followers}
</div>
</Show>
<Show when={props.topic.stat.shouts}>
<div>
{t('Publications')}: {props.topic.stat.shouts}
</div>
</Show>
</span>
</a>
<Show when={isAuthenticated()}>
<div class={styles.actions}>
<Show
when={!props.minimizeSubscribeButton}
fallback={
<CheckButton
text={t('Follow')}
checked={subscribed()}
onClick={() => subscribe(!subscribed)}
/>
}
>
<Show
when={subscribed()}
fallback={
<Button
variant="primary"
size="S"
value={isSubscribing() ? t('...subscribing') : t('Subscribe')}
onClick={() => subscribe(true)}
/>
}
>
<Button
onClick={() => subscribe(false)}
variant="secondary"
size="S"
value={t('You are subscribed')}
/>
</Show>
</Show>
</div>
</Show>
</div>
)
}

View File

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

View File

@ -1,4 +1,4 @@
import { Accessor, createEffect, createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js' import { Accessor, createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { Title } from '@solidjs/meta' import { Title } from '@solidjs/meta'

View File

@ -1,12 +1,9 @@
import { clsx } from 'clsx' import { clsx } from 'clsx'
// import styles from './ProfileSubscriptions.module.scss'
import { ProfileSettingsNavigation } from '../../Nav/ProfileSettingsNavigation' import { ProfileSettingsNavigation } from '../../Nav/ProfileSettingsNavigation'
import { createEffect, createSignal, For, onMount, Show } from 'solid-js' import { createEffect, createSignal, For, onMount, Show } from 'solid-js'
import { Loading } from '../../_shared/Loading' import { Loading } from '../../_shared/Loading'
import { SearchField } from '../../_shared/SearchField' import { SearchField } from '../../_shared/SearchField'
import { isAuthor } from '../../../utils/isAuthor' import { isAuthor } from '../../../utils/isAuthor'
import { AuthorCard } from '../../Author/AuthorCard'
import { TopicCard } from '../../Topic/Card'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session' import { useSession } from '../../../context/session'
import { Author, Topic } from '../../../graphql/types.gen' import { Author, Topic } from '../../../graphql/types.gen'
@ -16,6 +13,8 @@ import { dummyFilter } from '../../../utils/dummyFilter'
// TODO: refactor styles // TODO: refactor styles
import styles from '../../../pages/profile/Settings.module.scss' import styles from '../../../pages/profile/Settings.module.scss'
import stylesSettings from '../../../styles/FeedSettings.module.scss' import stylesSettings from '../../../styles/FeedSettings.module.scss'
import { AuthorBadge } from '../../Author/AuthorBadge'
import { TopicBadge } from '../../Topic/TopicBadge'
export const ProfileSubscriptions = () => { export const ProfileSubscriptions = () => {
const { t, lang } = useLocalize() const { t, lang } = useLocalize()
@ -104,23 +103,9 @@ export const ProfileSubscriptions = () => {
{(followingItem) => ( {(followingItem) => (
<div> <div>
{isAuthor(followingItem) ? ( {isAuthor(followingItem) ? (
<AuthorCard <AuthorBadge minimizeSubscribeButton={true} author={followingItem} />
author={followingItem}
hideWriteButton={true}
hasLink={true}
isTextButton={true}
truncateBio={true}
minimizeSubscribeButton={true}
/>
) : ( ) : (
<TopicCard <TopicBadge minimizeSubscribeButton={true} topic={followingItem} />
compact
isTopicInRow
showDescription
isCardMode
topic={followingItem}
minimizeSubscribeButton={true}
/>
)} )}
</div> </div>
)} )}

View File

@ -5,6 +5,7 @@
justify-content: center; justify-content: center;
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
white-space: nowrap;
&.primary { &.primary {
background: #000; background: #000;
@ -57,7 +58,10 @@
margin-right: 0.8em; margin-right: 0.8em;
min-width: auto !important; min-width: auto !important;
padding: 0; padding: 0;
transition: border-color 0.3s, background-color 0.3s, color 0.3s; transition:
border-color 0.3s,
background-color 0.3s,
color 0.3s;
&:hover, &:hover,
&:active { &:active {

View File

@ -4,6 +4,7 @@
justify-content: center; justify-content: center;
height: 32px; height: 32px;
min-width: 33px; min-width: 33px;
line-height: 32px;
box-sizing: border-box; box-sizing: border-box;
padding: 0 8px; padding: 0 8px;
background: var(--background-color); background: var(--background-color);

View File

@ -30,6 +30,8 @@ export default gql`
name name
slug slug
userpic userpic
createdAt
bio
} }
createdAt createdAt
publishedAt publishedAt

View File

@ -25,6 +25,7 @@ export type Author = {
about?: Maybe<Scalars['String']> about?: Maybe<Scalars['String']>
bio?: Maybe<Scalars['String']> bio?: Maybe<Scalars['String']>
caption?: Maybe<Scalars['String']> caption?: Maybe<Scalars['String']>
createdAt?: Maybe<Scalars['DateTime']>
id: Scalars['Int'] id: Scalars['Int']
lastSeen?: Maybe<Scalars['DateTime']> lastSeen?: Maybe<Scalars['DateTime']>
links?: Maybe<Array<Maybe<Scalars['String']>>> links?: Maybe<Array<Maybe<Scalars['String']>>>
@ -116,6 +117,7 @@ export type LoadShoutsFilters = {
author?: InputMaybe<Scalars['String']> author?: InputMaybe<Scalars['String']>
body?: InputMaybe<Scalars['String']> body?: InputMaybe<Scalars['String']>
days?: InputMaybe<Scalars['Int']> days?: InputMaybe<Scalars['Int']>
excludeLayout?: InputMaybe<Scalars['String']>
layout?: InputMaybe<Scalars['String']> layout?: InputMaybe<Scalars['String']>
reacted?: InputMaybe<Scalars['Boolean']> reacted?: InputMaybe<Scalars['Boolean']>
title?: InputMaybe<Scalars['String']> title?: InputMaybe<Scalars['String']>
@ -589,20 +591,6 @@ export type ShoutInput = {
topics?: InputMaybe<Array<InputMaybe<TopicInput>>> topics?: InputMaybe<Array<InputMaybe<TopicInput>>>
} }
export type ShoutsFilterBy = {
author?: InputMaybe<Scalars['String']>
authors?: InputMaybe<Array<InputMaybe<Scalars['String']>>>
body?: InputMaybe<Scalars['String']>
days?: InputMaybe<Scalars['Int']>
layout?: InputMaybe<Scalars['String']>
slug?: InputMaybe<Scalars['String']>
stat?: InputMaybe<Scalars['String']>
title?: InputMaybe<Scalars['String']>
topic?: InputMaybe<Scalars['String']>
topics?: InputMaybe<Array<InputMaybe<Scalars['String']>>>
visibility?: InputMaybe<Scalars['String']>
}
export type Stat = { export type Stat = {
commented?: Maybe<Scalars['Int']> commented?: Maybe<Scalars['Int']>
ranking?: Maybe<Scalars['Int']> ranking?: Maybe<Scalars['Int']>

View File

@ -177,7 +177,7 @@ export const ProfileSettingsPage = () => {
value={(value) => updateFormField('bio', value)} value={(value) => updateFormField('bio', value)}
initialValue={form.bio} initialValue={form.bio}
allowEnterKey={false} allowEnterKey={false}
maxLength={80} maxLength={120}
/> />
<h4>{t('About myself')}</h4> <h4>{t('About myself')}</h4>

View File

@ -31,9 +31,11 @@
// names from figma // names from figma
--black-50: #f7f7f8; --black-50: #f7f7f8;
--black-100: #e9e9ee; --black-100: #e9e9ee;
--black-300: #9fa1a7;
--black-500: #141414; --black-500: #141414;
--black-400: #696969; --black-400: #696969;
--white-500: #fff; --white-500: #fff;
--blue-500: #2638d9;
} }
[data-editor-dark-mode='true'] { [data-editor-dark-mode='true'] {