postmerge
This commit is contained in:
commit
c652c4ee84
|
@ -17,8 +17,10 @@
|
|||
"lint:code:fix": "eslint . --fix",
|
||||
"lint:styles": "stylelint **/*.{scss,css}",
|
||||
"lint:styles:fix": "stylelint **/*.{scss,css} --fix",
|
||||
"pre-commit": "lint-staged",
|
||||
"pre-push": "npm run typecheck",
|
||||
"pre-commit": "",
|
||||
"pre-push": "",
|
||||
"pre-commit-old": "lint-staged",
|
||||
"pre-push-old": "npm run typecheck",
|
||||
"prepare": "husky install",
|
||||
"preview": "astro preview",
|
||||
"server": "node server/server.mjs",
|
||||
|
|
5
public/icons/arrows-rotate.svg
Normal file
5
public/icons/arrows-rotate.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512">
|
||||
<path
|
||||
d="M105.1 202.6c7.7-21.8 20.2-42.3 37.8-59.8c62.5-62.5 163.8-62.5 226.3 0L386.3 160H336c-17.7 0-32 14.3-32 32s14.3 32 32 32H463.5c0 0 0 0 0 0h.4c17.7 0 32-14.3 32-32V64c0-17.7-14.3-32-32-32s-32 14.3-32 32v51.2L414.4 97.6c-87.5-87.5-229.3-87.5-316.8 0C73.2 122 55.6 150.7 44.8 181.4c-5.9 16.7 2.9 34.9 19.5 40.8s34.9-2.9 40.8-19.5zM39 289.3c-5 1.5-9.8 4.2-13.7 8.2c-4 4-6.7 8.8-8.1 14c-.3 1.2-.6 2.5-.8 3.8c-.3 1.7-.4 3.4-.4 5.1V448c0 17.7 14.3 32 32 32s32-14.3 32-32V396.9l17.6 17.5 0 0c87.5 87.4 229.3 87.4 316.7 0c24.4-24.4 42.1-53.1 52.9-83.7c5.9-16.7-2.9-34.9-19.5-40.8s-34.9 2.9-40.8 19.5c-7.7 21.8-20.2 42.3-37.8 59.8c-62.5 62.5-163.8 62.5-226.3 0l-.1-.1L125.6 352H176c17.7 0 32-14.3 32-32s-14.3-32-32-32H48.4c-1.6 0-3.2 .1-4.8 .3s-3.1 .5-4.6 1z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 842 B |
4
public/icons/instagram-white.svg
Normal file
4
public/icons/instagram-white.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M7.92 6C7.92 6.37974 7.80739 6.75095 7.59642 7.06669C7.38545 7.38244 7.08559 7.62853 6.73475 7.77385C6.38392 7.91917 5.99787 7.95719 5.62543 7.88311C5.25298 7.80902 4.91087 7.62616 4.64235 7.35764C4.37384 7.08913 4.19098 6.74702 4.11689 6.37457C4.04281 6.00213 4.08083 5.61608 4.22615 5.26525C4.37147 4.91441 4.61756 4.61455 4.93331 4.40358C5.24905 4.19261 5.62026 4.08 6 4.08C6.50904 4.08058 6.99706 4.28305 7.357 4.643C7.71695 5.00294 7.91942 5.49096 7.92 6ZM12 3.36V8.64C11.999 9.53082 11.6447 10.3849 11.0148 11.0148C10.3849 11.6447 9.53082 11.999 8.64 12H3.36C2.46918 11.999 1.61514 11.6447 0.985236 11.0148C0.355332 10.3849 0.00100889 9.53082 0 8.64V3.36C0.00100889 2.46918 0.355332 1.61514 0.985236 0.985236C1.61514 0.355332 2.46918 0.00100889 3.36 0H8.64C9.53082 0.00100889 10.3849 0.355332 11.0148 0.985236C11.6447 1.61514 11.999 2.46918 12 3.36ZM8.88 6C8.88 5.43039 8.71109 4.87357 8.39463 4.39996C8.07817 3.92634 7.62838 3.55721 7.10213 3.33923C6.57588 3.12125 5.99681 3.06421 5.43814 3.17534C4.87947 3.28646 4.36631 3.56076 3.96353 3.96353C3.56076 4.36631 3.28646 4.87947 3.17534 5.43814C3.06421 5.99681 3.12125 6.57588 3.33923 7.10213C3.55721 7.62838 3.92634 8.07817 4.39996 8.39463C4.87357 8.71109 5.43039 8.88 6 8.88C6.76356 8.87914 7.4956 8.57543 8.03551 8.03551C8.57543 7.4956 8.87914 6.76356 8.88 6ZM9.84 2.88C9.84 2.7376 9.79777 2.59839 9.71866 2.47999C9.63954 2.36159 9.52709 2.2693 9.39553 2.21481C9.26397 2.16031 9.1192 2.14605 8.97954 2.17383C8.83987 2.20162 8.71158 2.27019 8.61088 2.37088C8.51019 2.47158 8.44162 2.59987 8.41383 2.73953C8.38605 2.8792 8.40031 3.02397 8.45481 3.15553C8.5093 3.28709 8.60159 3.39954 8.71999 3.47866C8.83839 3.55777 8.9776 3.6 9.12 3.6C9.31096 3.6 9.49409 3.52414 9.62912 3.38912C9.76414 3.25409 9.84 3.07096 9.84 2.88Z" fill="#fff"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
4
public/icons/link-white.svg
Normal file
4
public/icons/link-white.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M2.8792 14.3342C1.67 13.125 1.67 11.1592 2.8792 9.94999L5.70771 7.12147L4.36417 5.77792L1.53565 8.60644C-0.416029 10.5581 -0.416029 13.7261 1.53565 15.6777C3.48733 17.6294 6.65527 17.6294 8.60695 15.6777L11.4355 12.8492L10.0919 11.5057L7.2634 14.3342C6.05421 15.5434 4.08839 15.5434 2.8792 14.3342ZM6.48498 12.1424L12.142 6.4854L10.7278 5.07114L5.07072 10.7282L6.48498 12.1424ZM5.77797 4.36412L8.60649 1.5356C10.5582 -0.416075 13.7261 -0.416075 15.6778 1.5356C17.6295 3.48728 17.6295 6.65522 15.6778 8.6069L12.8493 11.4354L11.5057 10.0919L14.3342 7.26335C15.5434 6.05416 15.5434 4.08834 14.3342 2.87915C13.125 1.66996 11.1592 1.66996 9.95003 2.87915L7.12151 5.70767L5.77797 4.36412Z" fill="#fff"/>
|
||||
</svg>
|
After Width: | Height: | Size: 812 B |
|
@ -37,7 +37,6 @@ const formatDate = (date: Date) => {
|
|||
}
|
||||
|
||||
export const FullArticle = (props: ArticleProps) => {
|
||||
const body = createMemo(() => props.article?.body?.toString().trim())
|
||||
const { session } = useAuthStore()
|
||||
|
||||
onMount(() => {
|
||||
|
@ -93,9 +92,16 @@ export const FullArticle = (props: ArticleProps) => {
|
|||
<div class="shout__cover" style={{ 'background-image': `url('${props.article.cover}')` }} />
|
||||
</div>
|
||||
|
||||
<div class="shout__body">
|
||||
<MD body={body()} />
|
||||
</div>
|
||||
<Show when={Boolean(props.article.body)}>
|
||||
<div class="shout__body">
|
||||
<Show
|
||||
when={!props.article.body.startsWith('<')}
|
||||
fallback={<div innerHTML={props.article.body} />}
|
||||
>
|
||||
<MD body={props.article.body} />
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</article>
|
||||
|
||||
<div class="col-md-8 shift-content">
|
||||
|
|
|
@ -8,30 +8,30 @@
|
|||
}
|
||||
}
|
||||
|
||||
.author__details {
|
||||
.authorDetails {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding-right: 1.2rem;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.author__details-wrapper {
|
||||
.authorDetailsWrapper {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.author__name {
|
||||
.authorName {
|
||||
border: none !important;
|
||||
font-size: 1.7rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
.author__about {
|
||||
.authorAbout {
|
||||
font-size: 1.5rem;
|
||||
color: rgb(0 0 0 / 60%);
|
||||
}
|
||||
|
||||
.author__subscribe {
|
||||
.authorSubscribe {
|
||||
@include media-breakpoint-down(lg) {
|
||||
padding: 0 0 0 42px;
|
||||
}
|
||||
|
@ -43,6 +43,7 @@
|
|||
height: 32px;
|
||||
margin-right: 0.4rem;
|
||||
position: relative;
|
||||
transition: background-color 0.2s;
|
||||
vertical-align: middle;
|
||||
width: 32px;
|
||||
|
||||
|
@ -57,8 +58,17 @@
|
|||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: filter 0.2s;
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #000;
|
||||
|
||||
&::before {
|
||||
filter: invert(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a[href*='facebook.com/'] {
|
||||
|
@ -90,9 +100,15 @@
|
|||
background-image: url(/icons/tumblr-white.svg);
|
||||
}
|
||||
}
|
||||
|
||||
a[href*='instagram.com/'] {
|
||||
&::before {
|
||||
background-image: url(/icons/instagram-white.svg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button--subscribe {
|
||||
.buttonSubscribe {
|
||||
align-items: center;
|
||||
aspect-ratio: 1/1;
|
||||
background: #f6f6f6;
|
||||
|
@ -105,14 +121,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
.button__label {
|
||||
.buttonLabel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.button--write {
|
||||
.buttonWrite {
|
||||
background: #f7f7f7;
|
||||
color: #000;
|
||||
display: inline-flex;
|
||||
|
||||
@include font-size(1.5rem);
|
||||
|
||||
.icon {
|
||||
|
@ -123,3 +140,62 @@
|
|||
height: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.authorPage {
|
||||
.authorName {
|
||||
@include font-size(3.4rem);
|
||||
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
|
||||
.authorAbout {
|
||||
color: #696969;
|
||||
@include font-size(1.7rem);
|
||||
}
|
||||
|
||||
.authorSubscribe {
|
||||
display: flex;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.authorDetails {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.buttonLabel {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.buttonSubscribe {
|
||||
aspect-ratio: auto;
|
||||
background-color: #000;
|
||||
border-radius: 2px;
|
||||
float: none;
|
||||
padding-bottom: 0.6rem;
|
||||
padding-top: 0.6rem;
|
||||
|
||||
.icon {
|
||||
margin-right: 0.5em;
|
||||
|
||||
img {
|
||||
filter: invert(1);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.icon img {
|
||||
filter: invert(0.7);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buttonSubscribe img {
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin-right: 1.6rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
|
@ -2,13 +2,14 @@ import { For, Show } from 'solid-js/web'
|
|||
import type { Author } from '../../graphql/types.gen'
|
||||
import Userpic from './Userpic'
|
||||
import { Icon } from '../Nav/Icon'
|
||||
import './Card.scss'
|
||||
import style from './Card.module.scss'
|
||||
import { createMemo } from 'solid-js'
|
||||
import { translit } from '../../utils/ru2en'
|
||||
import { t } from '../../utils/intl'
|
||||
import { useAuthStore } from '../../stores/auth'
|
||||
import { locale } from '../../stores/ui'
|
||||
import { follow, unfollow } from '../../stores/zine/common'
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
interface AuthorCardProps {
|
||||
compact?: boolean
|
||||
|
@ -17,6 +18,7 @@ interface AuthorCardProps {
|
|||
hasLink?: boolean
|
||||
subscribed?: boolean
|
||||
author: Author
|
||||
isAuthorPage?: boolean
|
||||
}
|
||||
|
||||
export const AuthorCard = (props: AuthorCardProps) => {
|
||||
|
@ -34,45 +36,51 @@ export const AuthorCard = (props: AuthorCardProps) => {
|
|||
}
|
||||
// TODO: reimplement AuthorCard
|
||||
return (
|
||||
<div class="author">
|
||||
<Userpic user={props.author} hasLink={props.hasLink} />
|
||||
<div class={style.author} classList={{ [style.authorPage]: props.isAuthorPage }}>
|
||||
<Userpic user={props.author} hasLink={props.hasLink} isBig={props.isAuthorPage} />
|
||||
|
||||
<div class="author__details">
|
||||
<div class="author__details-wrapper">
|
||||
<div class={style.authorDetails}>
|
||||
<div class={style.authorDetailsWrapper}>
|
||||
<Show when={props.hasLink}>
|
||||
<a class="author__name text-3xl text-2xl" href={`/author/${props.author.slug}`}>
|
||||
<a class={style.authorName} href={`/author/${props.author.slug}`}>
|
||||
{name()}
|
||||
</a>
|
||||
</Show>
|
||||
<Show when={!props.hasLink}>
|
||||
<div class="author__name text-3xl text-2xl">{name()}</div>
|
||||
<div class={style.authorName}>{name()}</div>
|
||||
</Show>
|
||||
|
||||
<Show when={!props.hideDescription}>
|
||||
<div class="author__about">{bio()}</div>
|
||||
<div class={style.authorAbout}>{bio()}</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<Show when={canFollow()}>
|
||||
<div class="author__subscribe">
|
||||
<div class={style.authorSubscribe}>
|
||||
<Show
|
||||
when={subscribed()}
|
||||
fallback={
|
||||
<button onClick={() => follow} class="button button--subscribe">
|
||||
<Icon name="author-subscribe" />
|
||||
<span class="button__label">+ {t('Follow')}</span>
|
||||
<button
|
||||
onClick={() => follow}
|
||||
class={clsx('button button--subscribe', style.button, style.buttonSubscribe)}
|
||||
>
|
||||
<Icon name="author-subscribe" class={style.icon} />
|
||||
<span class={style.buttonLabel}> {t('Follow')}</span>
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<button onClick={() => unfollow} class="button button--subscribe">
|
||||
<Icon name="author-unsubscribe" />
|
||||
<span class="button__label">- {t('Unfollow')}</span>
|
||||
<button
|
||||
onClick={() => unfollow}
|
||||
class={clsx('button button--subscribe', style.button, style.buttonSubscribe)}
|
||||
>
|
||||
<Icon name="author-unsubscribe" class={style.icon} />
|
||||
<span class={style.buttonLabel}>- {t('Unfollow')}</span>
|
||||
</button>
|
||||
</Show>
|
||||
|
||||
<Show when={!props.compact}>
|
||||
<button class="button button--write">
|
||||
<Icon name="edit" />
|
||||
<button class={clsx(style.buttonWrite, style.button, 'button')}>
|
||||
<Icon name="edit" class={style.icon} />
|
||||
{t('Write')}
|
||||
</button>
|
||||
|
||||
|
|
|
@ -1,67 +1,6 @@
|
|||
.user-details {
|
||||
margin-bottom: 4.4rem;
|
||||
padding: 0;
|
||||
|
||||
.author__name {
|
||||
@include font-size(3.4rem);
|
||||
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
|
||||
.author__about {
|
||||
color: #696969;
|
||||
@include font-size(1.7rem);
|
||||
}
|
||||
|
||||
.circlewrap {
|
||||
margin-right: 4.8rem;
|
||||
max-width: 168px;
|
||||
min-width: 168px;
|
||||
height: 168px;
|
||||
width: 168px;
|
||||
}
|
||||
|
||||
.circlewrap .userpic {
|
||||
font-size: 2em;
|
||||
line-height: 168px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.author__subscribe {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.author__details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.button--subscribe {
|
||||
aspect-ratio: auto;
|
||||
background-color: #000;
|
||||
border-radius: 2px;
|
||||
float: none;
|
||||
padding-bottom: 0.6rem;
|
||||
padding-top: 0.6rem;
|
||||
}
|
||||
|
||||
.button__label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.button--subscribe .icon {
|
||||
filter: invert(1);
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.button--subscribe img {
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin-right: 1.6rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.author-page {
|
||||
|
|
|
@ -7,7 +7,7 @@ export default (props: { author: Author }) => {
|
|||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="user-details">
|
||||
<AuthorCard author={props.author} compact={false} />
|
||||
<AuthorCard author={props.author} compact={false} isAuthorPage={true} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -33,3 +33,17 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.big.circlewrap {
|
||||
margin-right: 4.8rem;
|
||||
max-width: 168px;
|
||||
min-width: 168px;
|
||||
height: 168px;
|
||||
width: 168px;
|
||||
|
||||
.userpic {
|
||||
font-size: 2em;
|
||||
line-height: 168px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
import { Show } from 'solid-js/web'
|
||||
import type { Author } from '../../graphql/types.gen'
|
||||
import './Userpic.scss'
|
||||
import style from './Userpic.module.scss'
|
||||
|
||||
interface UserpicProps {
|
||||
user: Author
|
||||
hasLink?: boolean
|
||||
isBig?: boolean
|
||||
}
|
||||
|
||||
export default (props: UserpicProps) => {
|
||||
|
@ -15,7 +16,7 @@ export default (props: UserpicProps) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div class="circlewrap">
|
||||
<div class={style.circlewrap} classList={{ [style.big]: props.isBig }}>
|
||||
<Show when={props.hasLink}>
|
||||
<a href={`/author/${props.user.slug}`}>
|
||||
<Show
|
||||
|
@ -28,7 +29,7 @@ export default (props: UserpicProps) => {
|
|||
/>
|
||||
}
|
||||
>
|
||||
<div class="userpic">{letters()}</div>
|
||||
<div class={style.userpic}>{letters()}</div>
|
||||
</Show>
|
||||
</a>
|
||||
</Show>
|
||||
|
@ -44,7 +45,7 @@ export default (props: UserpicProps) => {
|
|||
/>
|
||||
}
|
||||
>
|
||||
<div class="userpic">{letters()}</div>
|
||||
<div class={style.userpic}>{letters()}</div>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
// import menu from './extension/menu'
|
||||
// import scroll from './prosemirror/extension/scroll'
|
||||
import { keymap } from 'prosemirror-keymap'
|
||||
import type { ProseMirrorExtension } from './helpers'
|
||||
import { Schema } from 'prosemirror-model'
|
||||
import type { Command } from 'prosemirror-state'
|
||||
import { t } from '../../../utils/intl'
|
||||
import base from './extension/base'
|
||||
import code from './extension/code'
|
||||
import collab from './extension/collab'
|
||||
import dragHandle from './extension/drag-handle'
|
||||
import image from './extension/image'
|
||||
import link from './extension/link'
|
||||
import markdown from './extension/markdown'
|
||||
import pasteMarkdown from './extension/paste-markdown'
|
||||
import placeholder from './extension/placeholder'
|
||||
import selectionMenu from './extension/selection'
|
||||
import strikethrough from './extension/strikethrough'
|
||||
import table from './extension/table'
|
||||
import todoList from './extension/todo-list'
|
||||
import collab from './extension/collab'
|
||||
import type { Config, YOptions } from '../store/context'
|
||||
import type { ProseMirrorExtension } from './helpers'
|
||||
import selectionMenu from './extension/selection'
|
||||
import type { Command } from 'prosemirror-state'
|
||||
import placeholder from './extension/placeholder'
|
||||
import todoList from './extension/todo-list'
|
||||
import strikethrough from './extension/strikethrough'
|
||||
|
||||
interface ExtensionsProps {
|
||||
data?: unknown
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.beside-column {
|
||||
.besideColumn {
|
||||
counter-reset: item;
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
|
@ -29,17 +29,6 @@
|
|||
text-align: center;
|
||||
width: 2em;
|
||||
}
|
||||
|
||||
.shout-card--short {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 0;
|
||||
|
||||
.shout-card__title,
|
||||
.shout-card__subtitle {
|
||||
display: inline;
|
||||
@include font-size(1.4rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,7 +40,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.beside-column-title {
|
||||
.besideColumnTitle {
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
|
@ -87,14 +76,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
.beside-column__topic {
|
||||
.besideColumnTopic {
|
||||
font-size: 1.2rem;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 0.4rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.beside-column__shout {
|
||||
.besideColumnShout {
|
||||
font-size: 1.4rem;
|
||||
|
||||
h4 {
|
|
@ -4,7 +4,7 @@ import { For, Show } from 'solid-js/web'
|
|||
import { ArticleCard } from './Card'
|
||||
import { AuthorCard } from '../Author/Card'
|
||||
import { TopicCard } from '../Topic/Card'
|
||||
import './Beside.scss'
|
||||
import style from './Beside.module.scss'
|
||||
import type { Author, Shout, Topic, User } from '../../graphql/types.gen'
|
||||
import { Icon } from '../Nav/Icon'
|
||||
import { t } from '../../utils/intl'
|
||||
|
@ -15,8 +15,10 @@ interface BesideProps {
|
|||
beside: Shout
|
||||
wrapper: 'topic' | 'author' | 'article' | 'top-article'
|
||||
isTopicCompact?: boolean
|
||||
isTopicInRow?: boolean
|
||||
topicShortDescription?: boolean
|
||||
topicsBySlug?: { [slug: string]: Topic }
|
||||
iconButton?: boolean
|
||||
}
|
||||
|
||||
export default (props: BesideProps) => {
|
||||
|
@ -27,7 +29,7 @@ export default (props: BesideProps) => {
|
|||
<Show when={!!props.values}>
|
||||
<div class="col-md-4">
|
||||
<Show when={!!props.title}>
|
||||
<div class="beside-column-title">
|
||||
<div class={style.besideColumnTitle}>
|
||||
<h4>{props.title}</h4>
|
||||
|
||||
<Show when={props.wrapper === 'author'}>
|
||||
|
@ -38,15 +40,17 @@ export default (props: BesideProps) => {
|
|||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
<ul class="beside-column">
|
||||
<ul class={style.besideColumn}>
|
||||
<For each={[...props.values]}>
|
||||
{(value: Shout | User | Topic | Author) => (
|
||||
<li classList={{ top: props.wrapper.startsWith('top-') }}>
|
||||
<li classList={{ [style.top]: props.wrapper.startsWith('top-') }}>
|
||||
<Show when={props.wrapper === 'topic'}>
|
||||
<TopicCard
|
||||
topic={value as Topic}
|
||||
compact={props.isTopicCompact}
|
||||
shortDescription={props.topicShortDescription}
|
||||
isTopicInRow={props.isTopicInRow}
|
||||
iconButton={props.iconButton}
|
||||
/>
|
||||
</Show>
|
||||
<Show when={props.wrapper === 'author'}>
|
||||
|
@ -58,7 +62,7 @@ export default (props: BesideProps) => {
|
|||
<Show when={props.wrapper === 'top-article' && value?.slug}>
|
||||
<ArticleCard
|
||||
article={value as Shout}
|
||||
settings={{ noimage: true, noauthor: true, nodate: true }}
|
||||
settings={{ noimage: true, noauthor: true, nodate: true, isShort: true }}
|
||||
/>
|
||||
</Show>
|
||||
</li>
|
||||
|
@ -68,7 +72,7 @@ export default (props: BesideProps) => {
|
|||
</div>
|
||||
</Show>
|
||||
<div class="col-md-8">
|
||||
<ArticleCard article={props.beside} settings={{}} />
|
||||
<ArticleCard article={props.beside} settings={{ isBigTitle: true }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,22 +1,12 @@
|
|||
.shout-card {
|
||||
.shoutCard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 2.4rem;
|
||||
position: relative;
|
||||
|
||||
.floor--1 &:nth-child(2) {
|
||||
border-top: 1px solid #141414;
|
||||
margin-top: 2.4rem !important;
|
||||
padding-top: 2.4rem;
|
||||
|
||||
.shout-card__cover {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.shout-card__cover img {
|
||||
.shoutCardCover img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +16,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.shout-card__titles-container {
|
||||
.shoutCardWithBorder {
|
||||
border-top: 1px solid #141414;
|
||||
margin-top: 2.4rem !important;
|
||||
padding-top: 2.4rem;
|
||||
}
|
||||
|
||||
.shoutCardTitlesContainer {
|
||||
a {
|
||||
&::before {
|
||||
content: '';
|
||||
|
@ -39,7 +35,7 @@
|
|||
}
|
||||
|
||||
&:hover {
|
||||
.shout-card__title .shout-card__link-container {
|
||||
.shoutCardTitle .shoutCardLinkContainer {
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
}
|
||||
|
@ -47,11 +43,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.shout-card__cover-container {
|
||||
.shoutCardCoverContainer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.shout-card__cover {
|
||||
.shoutCardCover {
|
||||
height: 0;
|
||||
margin-bottom: 1.6rem;
|
||||
overflow: hidden;
|
||||
|
@ -73,25 +69,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
.shout__topic,
|
||||
.shout__author {
|
||||
a {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.shout__author,
|
||||
.shout__date {
|
||||
.shoutAuthor,
|
||||
.shoutDate {
|
||||
@include font-size(1.2rem);
|
||||
}
|
||||
|
||||
.shout__author {
|
||||
.shoutAuthor {
|
||||
margin-right: 1.6rem;
|
||||
|
||||
a {
|
||||
border: none;
|
||||
color: rgb(0 0 0 / 70%);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
|
@ -99,15 +89,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
.shout__date {
|
||||
.shoutDate {
|
||||
color: rgb(0 0 0 / 50%);
|
||||
}
|
||||
|
||||
.shout__details {
|
||||
.shoutDetails {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.shout-card__title {
|
||||
.shoutCardTitle {
|
||||
@include font-size(2.2rem);
|
||||
|
||||
font-weight: 700;
|
||||
|
@ -115,29 +105,33 @@
|
|||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
.shout-card__title,
|
||||
.shout-card__subtitle {
|
||||
.shoutCardTitle,
|
||||
.shoutCardSubtitle {
|
||||
&,
|
||||
.shout-card__link-container {
|
||||
.shoutCardLinkContainer {
|
||||
box-decoration-break: clone;
|
||||
|
||||
/* stylelint-disable-next-line */
|
||||
-webkit-box-decoration-break: clone;
|
||||
}
|
||||
}
|
||||
|
||||
.shout-card__subtitle {
|
||||
.shoutCardSubtitle {
|
||||
color: #696969;
|
||||
@include font-size(1.7rem);
|
||||
|
||||
font-weight: 400;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 0.8rem;
|
||||
transition: color 0.2s, background-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.shout-card__link-container {
|
||||
.shoutCardLinkContainer {
|
||||
position: relative;
|
||||
transition: color 0.2s, background-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.shout-card__edit-control {
|
||||
.shoutCardEditControl {
|
||||
border-radius: 2em;
|
||||
min-height: 2.6em;
|
||||
padding: 0 1.4em;
|
||||
|
@ -149,17 +143,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
.shout-card.withcover {
|
||||
.shoutCard.withcover {
|
||||
padding: 2.4rem;
|
||||
|
||||
&,
|
||||
a,
|
||||
.shout-card__title,
|
||||
.shout-card__subtitle {
|
||||
.shoutCardTitle,
|
||||
.shoutCardSubtitle {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.shout-card__cover {
|
||||
.shoutCardCover {
|
||||
height: 100%;
|
||||
left: 0;
|
||||
padding: 0;
|
||||
|
@ -178,7 +172,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.shout-card__type {
|
||||
.shoutCardType {
|
||||
background: #fff;
|
||||
border-radius: 100%;
|
||||
height: 3.2rem;
|
||||
|
@ -213,7 +207,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.shout-card__content {
|
||||
.shoutCardContent {
|
||||
margin-bottom: 1em;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
|
@ -229,7 +223,7 @@
|
|||
}
|
||||
|
||||
.col-md-6 .col-md-6 {
|
||||
.shout-card {
|
||||
.shoutCard {
|
||||
border-bottom: 1px solid rgb(255 255 255 / 20%);
|
||||
margin: 3.6rem 0 0;
|
||||
padding-bottom: 2rem;
|
||||
|
@ -244,71 +238,56 @@
|
|||
}
|
||||
}
|
||||
|
||||
.shout-card__cover-container {
|
||||
.shoutCardCoverContainer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.shout-card__title,
|
||||
.shout-card__subtitle {
|
||||
.shoutCardTitle,
|
||||
.shoutCardSubtitle {
|
||||
display: inline;
|
||||
@include font-size(2.6rem);
|
||||
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.shout-card__subtitle {
|
||||
.shoutCardSubtitle {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.shout__author {
|
||||
.shoutAuthor {
|
||||
margin-top: 0.6em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.floor--important {
|
||||
padding-bottom: $container-padding-x;
|
||||
padding-top: $container-padding-x;
|
||||
.shoutCardFloorImportant {
|
||||
margin-bottom: $grid-gutter-width;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
padding-bottom: $grid-gutter-width;
|
||||
padding-top: $grid-gutter-width;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@include font-size(4.4rem);
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.shout-card {
|
||||
margin-bottom: $grid-gutter-width;
|
||||
}
|
||||
|
||||
.shout__author {
|
||||
.shoutAuthor {
|
||||
margin-top: 0.8rem;
|
||||
}
|
||||
|
||||
.col-md-6 {
|
||||
.shout-card__title,
|
||||
.shout-card__subtitle {
|
||||
display: inline;
|
||||
@include font-size(2.6rem);
|
||||
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.shout-card__title {
|
||||
margin-right: 0.3em;
|
||||
a {
|
||||
color: rgb(255 255 255 / 50%);
|
||||
}
|
||||
}
|
||||
|
||||
.shout-card__titles-container {
|
||||
.shoutCardTitle {
|
||||
a {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background: #fff;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
.shoutCardTitlesContainer {
|
||||
a {
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
.shout-card__title .shout-card__link-container {
|
||||
.shoutCardTitle .shoutCardLinkContainer {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
}
|
||||
|
@ -317,7 +296,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.shout-card--with-cover {
|
||||
.shoutCardWithCover {
|
||||
padding: 56.2% 2.4rem 0;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
|
@ -325,7 +304,7 @@
|
|||
}
|
||||
|
||||
&.swiper-slide {
|
||||
.shout-card__content {
|
||||
.shoutCardContent {
|
||||
@include media-breakpoint-down(md) {
|
||||
padding-left: 10%;
|
||||
}
|
||||
|
@ -334,25 +313,25 @@
|
|||
|
||||
&,
|
||||
a,
|
||||
.shout-card__title,
|
||||
.shout-card__subtitle {
|
||||
.shoutCardTitle,
|
||||
.shoutCardSubtitle {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.shout__author,
|
||||
.shout-card__titles-container {
|
||||
.shoutAuthor,
|
||||
.shoutCardTitlesContainer {
|
||||
a:hover {
|
||||
&,
|
||||
.shout-card__title .shout-card__link-container {
|
||||
.shoutCardTitle .shoutCardLinkContainer {
|
||||
background-color: #fff;
|
||||
color: #000 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.shout-card__cover-container,
|
||||
.shout-card__cover,
|
||||
.shout-card__content {
|
||||
.shoutCardCoverContainer,
|
||||
.shoutCardCover,
|
||||
.shoutCardContent {
|
||||
height: 100%;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
|
@ -362,7 +341,7 @@
|
|||
z-index: -1;
|
||||
}
|
||||
|
||||
.shout-card__content {
|
||||
.shoutCardContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: end;
|
||||
|
@ -370,7 +349,7 @@
|
|||
z-index: 1;
|
||||
}
|
||||
|
||||
.shout-card__cover {
|
||||
.shoutCardCover {
|
||||
padding: 0;
|
||||
|
||||
&::after {
|
||||
|
@ -383,43 +362,37 @@
|
|||
}
|
||||
}
|
||||
|
||||
.shout-card__title {
|
||||
.shoutCardTitle {
|
||||
@include font-size(3.2rem);
|
||||
}
|
||||
}
|
||||
|
||||
.shout-card--content-top {
|
||||
.shout-card__content {
|
||||
.shoutCardContentTop {
|
||||
.shoutCardContent {
|
||||
justify-content: start;
|
||||
}
|
||||
}
|
||||
|
||||
.shout-card--short {
|
||||
.shout-card__title {
|
||||
@include font-size(1.7rem);
|
||||
}
|
||||
}
|
||||
|
||||
.shout-card--photo-bottom {
|
||||
.shout-card__content {
|
||||
.shoutCardPhotoBottom {
|
||||
.shoutCardContent {
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
.shout-card__cover-container {
|
||||
.shoutCardCoverContainer {
|
||||
order: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.shout-card--feed {
|
||||
.shoutCardFeed {
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
margin-bottom: 2em;
|
||||
|
||||
.shout-card__content {
|
||||
.shoutCardContent {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.shout-card__details {
|
||||
.shoutCardDetails {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
|
@ -431,11 +404,11 @@
|
|||
z-index: 2;
|
||||
}
|
||||
|
||||
.shout-card__details-content {
|
||||
.shoutCardDetailsContent {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.shout-card__details-item {
|
||||
.shoutCardDetailsTtem {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-right: 1.7em;
|
||||
|
@ -454,7 +427,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.shout-card__comments {
|
||||
.shoutCardComments {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
|
@ -476,12 +449,12 @@
|
|||
display: flex;
|
||||
}
|
||||
|
||||
.rating__value {
|
||||
.ratingValue {
|
||||
font-weight: bold;
|
||||
margin: 0 0.5em;
|
||||
}
|
||||
|
||||
.rating__control {
|
||||
.ratingControl {
|
||||
align-items: center;
|
||||
border: 2px solid;
|
||||
border-radius: 100%;
|
||||
|
@ -501,32 +474,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
.floor--7 {
|
||||
@include media-breakpoint-down(md) {
|
||||
margin-bottom: 1em;
|
||||
.shoutCardVertical {
|
||||
aspect-ratio: auto;
|
||||
height: 100%;
|
||||
margin: 1.6rem 0;
|
||||
padding: 0 0 20%;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
aspect-ratio: 1 / 1.6;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.col-md-6 {
|
||||
margin-bottom: 1.6em;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.shout-card {
|
||||
aspect-ratio: auto;
|
||||
height: 100%;
|
||||
margin: 1.6rem 0;
|
||||
padding: 0 0 20%;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
aspect-ratio: 1 / 1.6;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.shout-card__title {
|
||||
.shoutCardTitle {
|
||||
font-size: 2.6rem !important;
|
||||
|
||||
@include media-breakpoint-between(lg, xl) {
|
||||
|
@ -534,11 +493,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.shout-card__content {
|
||||
.shoutCardContent {
|
||||
justify-content: start;
|
||||
position: relative;
|
||||
|
||||
.shout-card__subtitle {
|
||||
.shoutCardSubtitle {
|
||||
@include media-breakpoint-up(lg) {
|
||||
@include font-size(2rem);
|
||||
}
|
||||
|
@ -552,44 +511,151 @@
|
|||
|
||||
.floor--9 {
|
||||
/* TODO: refactor these styles */
|
||||
.shout-card__title,
|
||||
.shout-card__subtitle {
|
||||
.shoutCardTitle,
|
||||
.shoutCardSubtitle {
|
||||
// display: inline;
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
.shout-card__title {
|
||||
.shoutCardTitle {
|
||||
padding-right: 0.25em;
|
||||
}
|
||||
|
||||
.shout__author,
|
||||
.shout__date {
|
||||
.shoutAuthor,
|
||||
.shoutDate {
|
||||
margin-top: 0.8rem;
|
||||
}
|
||||
|
||||
.beside-column .shout-card__titles-container {
|
||||
.beside-column .shoutCardTitlesContainer {
|
||||
position: relative;
|
||||
|
||||
a:hover {
|
||||
.shout-card__link-container {
|
||||
.shoutCardLinkContainer {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.col-md-6,
|
||||
.col-lg-6,
|
||||
.col-md-8 {
|
||||
.shout-card__title {
|
||||
.shoutCardBigTitle {
|
||||
.shoutCardTitle {
|
||||
display: block;
|
||||
@include font-size(3.2rem);
|
||||
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.shout-card__subtitle {
|
||||
.shoutCardSubtitle {
|
||||
color: #696969;
|
||||
@include font-size(2.4rem);
|
||||
}
|
||||
}
|
||||
|
||||
.shoutCardCompact {
|
||||
.shoutCardTitle,
|
||||
.shoutCardSubtitle {
|
||||
display: inline;
|
||||
@include font-size(2.6rem);
|
||||
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.shoutCardSubtitle {
|
||||
padding-left: 0.3em;
|
||||
box-decoration-break: slice;
|
||||
|
||||
/* stylelint-disable-next-line */
|
||||
-webkit-box-decoration-break: slice;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
.shoutCardSubtitle {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.shoutCardShort {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 0;
|
||||
|
||||
.shoutCardTitle,
|
||||
.shoutCardSubtitle {
|
||||
display: inline;
|
||||
|
||||
@include font-size(1.4rem);
|
||||
}
|
||||
|
||||
.shoutCardTitle {
|
||||
padding-right: 0.25em;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
.shoutCardLinkContainer {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.shoutCardSingle {
|
||||
@include media-breakpoint-up(md) {
|
||||
flex-direction: row;
|
||||
|
||||
.shoutCardTitlesContainer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.shoutCardCover {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.shoutCardCoverContainer {
|
||||
flex: 1 58.3333%;
|
||||
}
|
||||
|
||||
.shoutCardContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 41.6666%;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0;
|
||||
padding-left: 4rem;
|
||||
}
|
||||
|
||||
.shoutTopic {
|
||||
margin-bottom: 3.2rem;
|
||||
}
|
||||
|
||||
.shoutCardTitle {
|
||||
margin-bottom: 2.4rem;
|
||||
}
|
||||
|
||||
.shoutAuthor {
|
||||
align-items: end;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.shoutCardType {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.shoutCardTitle {
|
||||
@include font-size(4rem);
|
||||
|
||||
font-weight: 900;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.shoutCardSubtitle {
|
||||
color: #696969;
|
||||
flex: 1;
|
||||
@include font-size(2.4rem);
|
||||
}
|
||||
}
|
|
@ -5,9 +5,11 @@ import type { Shout } from '../../graphql/types.gen'
|
|||
import { capitalize } from '../../utils'
|
||||
import { translit } from '../../utils/ru2en'
|
||||
import { Icon } from '../Nav/Icon'
|
||||
import './Card.scss'
|
||||
import style from './Card.module.scss'
|
||||
import { locale } from '../../stores/ui'
|
||||
import { handleClientRouteLinkClick } from '../../stores/router'
|
||||
import { clsx } from 'clsx'
|
||||
import CardTopic from './CardTopic'
|
||||
|
||||
interface ArticleCardProps {
|
||||
settings?: {
|
||||
|
@ -20,6 +22,14 @@ interface ArticleCardProps {
|
|||
photoBottom?: boolean
|
||||
additionalClass?: string
|
||||
isFeedMode?: boolean
|
||||
isFloorImportant?: boolean
|
||||
isWithCover?: boolean
|
||||
isBigTitle?: boolean
|
||||
isVertical?: boolean
|
||||
isShort?: boolean
|
||||
withBorder?: boolean
|
||||
isCompact?: boolean
|
||||
isSingle?: boolean
|
||||
}
|
||||
article: Shout
|
||||
}
|
||||
|
@ -62,56 +72,65 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
|||
|
||||
return (
|
||||
<section
|
||||
class={`shout-card ${props.settings?.additionalClass || ''}`}
|
||||
class={clsx(style.shoutCard, `${props.settings?.additionalClass || ''}`)}
|
||||
classList={{
|
||||
'shout-card--short': props.settings?.noimage,
|
||||
'shout-card--photo-bottom': props.settings?.noimage && props.settings?.photoBottom,
|
||||
'shout-card--feed': props.settings?.isFeedMode
|
||||
[style.shoutCardShort]: props.settings?.isShort,
|
||||
[style.shoutCardPhotoBottom]: props.settings?.noimage && props.settings?.photoBottom,
|
||||
[style.shoutCardFeed]: props.settings?.isFeedMode,
|
||||
[style.shoutCardFloorImportant]: props.settings?.isFloorImportant,
|
||||
[style.shoutCardWithCover]: props.settings?.isWithCover,
|
||||
[style.shoutCardBigTitle]: props.settings?.isBigTitle,
|
||||
[style.shoutCardVertical]: props.settings?.isVertical,
|
||||
[style.shoutCardWithBorder]: props.settings?.withBorder,
|
||||
[style.shoutCardCompact]: props.settings?.isCompact,
|
||||
[style.shoutCardSingle]: props.settings?.isSingle
|
||||
}}
|
||||
>
|
||||
<Show when={!props.settings?.noimage && cover}>
|
||||
<div class="shout-card__cover-container">
|
||||
<div class="shout-card__cover">
|
||||
<div class={style.shoutCardCoverContainer}>
|
||||
<div class={style.shoutCardCover}>
|
||||
<img src={cover || ''} alt={title || ''} loading="lazy" />
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<div class="shout-card__content">
|
||||
<div class={style.shoutCardContent}>
|
||||
<Show when={layout && layout !== 'article' && !(props.settings?.noicon || props.settings?.noimage)}>
|
||||
<div class="shout-card__type">
|
||||
<div class={style.shoutCardType}>
|
||||
<a href={`/topic/${mainTopic.slug}`}>
|
||||
<Icon name={layout} />
|
||||
<Icon name={layout} class={style.icon} />
|
||||
</a>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={!props.settings?.isGroup}>
|
||||
<div class="shout__topic">
|
||||
<a href={`/topic/${mainTopic.slug}`}>
|
||||
{locale() === 'ru' && mainTopic.title ? mainTopic.title : mainTopic.slug.replace('-', ' ')}
|
||||
</a>
|
||||
</div>
|
||||
<CardTopic
|
||||
title={
|
||||
locale() === 'ru' && mainTopic.title ? mainTopic.title : mainTopic.slug.replace('-', ' ')
|
||||
}
|
||||
slug={mainTopic.slug}
|
||||
isFloorImportant={props.settings?.isFloorImportant}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<div class="shout-card__titles-container">
|
||||
<div class={style.shoutCardTitlesContainer}>
|
||||
<a href={`/${slug || ''}`} onClick={handleClientRouteLinkClick}>
|
||||
<div class="shout-card__title">
|
||||
<span class="shout-card__link-container">{title}</span>
|
||||
<div class={style.shoutCardTitle}>
|
||||
<span class={style.shoutCardLinkContainer}>{title}</span>
|
||||
</div>
|
||||
|
||||
<Show when={!props.settings?.nosubtitle && subtitle}>
|
||||
<div class="shout-card__subtitle">
|
||||
<span class="shout-card__link-container">{subtitle}</span>
|
||||
<div class={style.shoutCardSubtitle}>
|
||||
<span class={style.shoutCardLinkContainer}>{subtitle}</span>
|
||||
</div>
|
||||
</Show>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<Show when={!props.settings?.noauthor || !props.settings?.nodate}>
|
||||
<div class="shout__details">
|
||||
<div class={style.shoutDetails}>
|
||||
<Show when={!props.settings?.noauthor}>
|
||||
<div class="shout__author">
|
||||
<div class={style.shoutAuthor}>
|
||||
<For each={authors}>
|
||||
{(author, index) => {
|
||||
const name =
|
||||
|
@ -131,39 +150,39 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
|||
</Show>
|
||||
|
||||
<Show when={!props.settings?.nodate}>
|
||||
<div class="shout__date">{formattedDate()}</div>
|
||||
<div class={style.shoutDate}>{formattedDate()}</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={props.settings?.isFeedMode}>
|
||||
<section class="shout-card__details">
|
||||
<div class="shout-card__details-content">
|
||||
<div class="shout-card__details-item rating">
|
||||
<section class={style.shoutCardDetails}>
|
||||
<div class={style.shoutCardDetailsContent}>
|
||||
<div class={clsx(style.shoutCardDetailsItem, 'rating')}>
|
||||
<button class="rating__control">−</button>
|
||||
<span class="rating__value">{stat?.rating || ''}</span>
|
||||
<button class="rating__control">+</button>
|
||||
</div>
|
||||
<div class="shout-card__details-item shout-card__comments">
|
||||
<Icon name="eye" />
|
||||
<div class={clsx(style.shoutCardDetailsItem, style.shoutCardComments)}>
|
||||
<Icon name="eye" class={style.icon} />
|
||||
{stat?.viewed}
|
||||
</div>
|
||||
<div class="shout-card__details-item shout-card__comments">
|
||||
<div class={clsx(style.shoutCardDetailsTtem, style.shoutCardComments)}>
|
||||
<a href={`/${slug + '#comments' || ''}`}>
|
||||
<Icon name="comment" />
|
||||
<Icon name="comment" class={style.icon} />
|
||||
{stat?.commented || ''}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="shout-card__details-item">
|
||||
<div class={style.shoutCardDetailsItem}>
|
||||
<button>
|
||||
<Icon name="bookmark" />
|
||||
<Icon name="bookmark" class={style.icon} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="shout-card__details-item">
|
||||
<div class={style.shoutCardDetailsItem}>
|
||||
<button>
|
||||
<Icon name="ellipsis" />
|
||||
<Icon name="ellipsis" class={style.icon} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
26
src/components/Feed/CardTopic.module.scss
Normal file
26
src/components/Feed/CardTopic.module.scss
Normal file
|
@ -0,0 +1,26 @@
|
|||
.shoutTopic {
|
||||
font-size: 1.2rem;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
a {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: $link-color;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
&:hover {
|
||||
background: $link-color;
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.shoutTopicFloorImportant {
|
||||
a {
|
||||
color: rgb(255 255 255 / 50%);
|
||||
}
|
||||
}
|
20
src/components/Feed/CardTopic.tsx
Normal file
20
src/components/Feed/CardTopic.tsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
import style from './CardTopic.module.scss'
|
||||
|
||||
interface CardTopicProps {
|
||||
title: string
|
||||
slug: string
|
||||
isFloorImportant?: boolean
|
||||
}
|
||||
|
||||
export default (props: CardTopicProps) => {
|
||||
return (
|
||||
<div
|
||||
class={style.shoutTopic}
|
||||
classList={{
|
||||
[style.shoutTopicFloorImportant]: props.isFloorImportant
|
||||
}}
|
||||
>
|
||||
<a href={`/topic/${props.slug}`}>{props.title}</a>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -33,3 +33,13 @@
|
|||
transition: filter 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
.floor--group {
|
||||
@include media-breakpoint-down(sm) {
|
||||
.col-lg-6 {
|
||||
.row {
|
||||
margin: 0 0 0 divide(-$container-padding-x, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,13 +12,16 @@ interface GroupProps {
|
|||
export default (props: GroupProps) => {
|
||||
if (!props.articles) props.articles = []
|
||||
return (
|
||||
<div class="floor floor--important">
|
||||
<div class="floor floor--important floor--group">
|
||||
<Show when={props.articles.length > 4}>
|
||||
<div class="wide-container row">
|
||||
<div class="group__header col-12">{props.header}</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<ArticleCard article={props.articles[0]} settings={{ nosubtitle: false, noicon: true }} />
|
||||
<ArticleCard
|
||||
article={props.articles[0]}
|
||||
settings={{ nosubtitle: false, noicon: true, isFloorImportant: true, isBigTitle: true }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
|
@ -28,7 +31,10 @@ export default (props: GroupProps) => {
|
|||
{(a) => (
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<ArticleCard article={a} settings={{ nosubtitle: false, noicon: true }} />
|
||||
<ArticleCard
|
||||
article={a}
|
||||
settings={{ nosubtitle: false, noicon: true, isBigTitle: true }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@ -37,12 +43,34 @@ export default (props: GroupProps) => {
|
|||
<Show when={props.articles.length >= 4}>
|
||||
<div class="col-md-6">
|
||||
<For each={props.articles.slice(1, 3)}>
|
||||
{(a) => <ArticleCard article={a} settings={{ noicon: true, noimage: true }} />}
|
||||
{(a) => (
|
||||
<ArticleCard
|
||||
article={a}
|
||||
settings={{
|
||||
noicon: true,
|
||||
noimage: true,
|
||||
isBigTitle: true,
|
||||
isCompact: true,
|
||||
isFloorImportant: true
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<For each={props.articles.slice(3, 5)}>
|
||||
{(a) => <ArticleCard article={a} settings={{ noicon: true, noimage: true }} />}
|
||||
{(a) => (
|
||||
<ArticleCard
|
||||
article={a}
|
||||
settings={{
|
||||
noicon: true,
|
||||
noimage: true,
|
||||
isBigTitle: true,
|
||||
isCompact: true,
|
||||
isFloorImportant: true
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
.floor--one-article {
|
||||
@include media-breakpoint-up(md) {
|
||||
.shout-card {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.shout-card__cover {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.shout-card__cover-container {
|
||||
flex: 1 58.3333%;
|
||||
}
|
||||
|
||||
.shout-card__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 41.6666%;
|
||||
justify-content: space-between;
|
||||
padding-left: 4rem;
|
||||
}
|
||||
|
||||
.shout__topic {
|
||||
margin-bottom: 3.2rem;
|
||||
}
|
||||
|
||||
.shout-card__title {
|
||||
margin-bottom: 2.4rem;
|
||||
}
|
||||
|
||||
.shout__author {
|
||||
align-items: end;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.shout-card__title {
|
||||
@include font-size(4rem);
|
||||
|
||||
font-weight: 900;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.shout-card__subtitle {
|
||||
color: #696969;
|
||||
flex: 1;
|
||||
@include font-size(2.4rem);
|
||||
}
|
||||
}
|
|
@ -1,14 +1,13 @@
|
|||
import { Show } from 'solid-js'
|
||||
import type { Shout } from '../../graphql/types.gen'
|
||||
import { ArticleCard } from './Card'
|
||||
import './Row1.scss'
|
||||
|
||||
export default (props: { article: Shout }) => (
|
||||
<Show when={!!props.article}>
|
||||
<div class="floor floor--one-article">
|
||||
<div class="wide-container row">
|
||||
<div class="col-12">
|
||||
<ArticleCard article={props.article} />
|
||||
<ArticleCard article={props.article} settings={{isSingle: true}} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -21,12 +21,7 @@ export default (props: { articles: Shout[] }) => {
|
|||
return (
|
||||
<Show when={!!a}>
|
||||
<div class={`col-md-${x[y()][i()]}`}>
|
||||
<ArticleCard
|
||||
article={a}
|
||||
settings={{
|
||||
additionalClass: x[y()][i()] === '8' ? 'shout-card--with-cover' : ''
|
||||
}}
|
||||
/>
|
||||
<ArticleCard article={a} settings={{ isWithCover: x[y()][i()] === '8' }} />
|
||||
</div>
|
||||
</Show>
|
||||
)
|
||||
|
|
|
@ -7,14 +7,14 @@ export const Row5 = (props: { articles: Shout[] }) => {
|
|||
<div class="wide-container row">
|
||||
<div class="col-md-3">
|
||||
<ArticleCard article={props.articles[0]} />
|
||||
<ArticleCard article={props.articles[1]} />
|
||||
<ArticleCard article={props.articles[1]} settings={{ noimage: true, withBorder: true }} />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<ArticleCard article={props.articles[2]} />
|
||||
<ArticleCard article={props.articles[2]} settings={{ isBigTitle: true }} />
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<ArticleCard article={props.articles[3]} />
|
||||
<ArticleCard article={props.articles[4]} />
|
||||
<ArticleCard article={props.articles[4]} settings={{ noimage: true, withBorder: true }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -10,7 +10,12 @@ export default (props: { articles: Shout[] }) => (
|
|||
<div class="col-md-6 col-lg-3">
|
||||
<ArticleCard
|
||||
article={a}
|
||||
settings={{ additionalClass: 'shout-card--with-cover shout-card--content-top' }}
|
||||
settings={{
|
||||
additionalClass: 'shout-card--content-top',
|
||||
isWithCover: true,
|
||||
isBigTitle: true,
|
||||
isVertical: true
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -1,17 +1,3 @@
|
|||
.floor--slider {
|
||||
.shout-card {
|
||||
.shout-card__cover-container {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.shout-card__cover {
|
||||
&::after {
|
||||
background: linear-gradient(0deg, rgb(0 0 0 / 80%) 0%, rgb(0 0 0 / 0%) 75%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.swiper-slide {
|
||||
height: 0 !important;
|
||||
min-height: 0 !important;
|
||||
|
|
|
@ -60,7 +60,12 @@ export default (props: SliderProps) => {
|
|||
{(a: Shout) => (
|
||||
<ArticleCard
|
||||
article={a}
|
||||
settings={{ additionalClass: 'shout-card--with-cover swiper-slide' }}
|
||||
settings={{
|
||||
additionalClass: 'swiper-slide',
|
||||
isFloorImportant: true,
|
||||
isWithCover: true,
|
||||
nodate: true
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
|
|
27
src/components/Loading.module.scss
Normal file
27
src/components/Loading.module.scss
Normal file
|
@ -0,0 +1,27 @@
|
|||
.container {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
animation-name: spin;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background-image: url(/icons/arrows-rotate.svg);
|
||||
background-repeat: no-repeat;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
}
|
9
src/components/Loading.tsx
Normal file
9
src/components/Loading.tsx
Normal file
|
@ -0,0 +1,9 @@
|
|||
import styles from './Loading.module.scss'
|
||||
|
||||
export const Loading = () => {
|
||||
return (
|
||||
<div class={styles.container}>
|
||||
<div class={styles.icon} />
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -9,6 +9,7 @@ import { useAuthStore, signIn, register } from '../../stores/auth'
|
|||
import { useValidator } from '../../utils/validators'
|
||||
import { baseUrl } from '../../graphql/publicGraphQLClient'
|
||||
import { ApiError } from '../../utils/apiClient'
|
||||
import { handleClientRouteLinkClick } from '../../stores/router'
|
||||
|
||||
type AuthMode = 'sign-in' | 'sign-up' | 'forget' | 'reset' | 'resend' | 'password'
|
||||
|
||||
|
@ -30,9 +31,16 @@ const titles = {
|
|||
|
||||
// const isProperEmail = (email) => email && email.length > 5 && email.includes('@') && email.includes('.')
|
||||
|
||||
// 3rd party provider auth handler
|
||||
const oauth = (provider: string): void => {
|
||||
const popup = window.open(`${baseUrl}/oauth/${provider}`, provider, 'width=740, height=420')
|
||||
popup?.focus()
|
||||
hideModal()
|
||||
}
|
||||
|
||||
// FIXME !!!
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
export default (props: { code?: string; mode?: string }) => {
|
||||
export default (props: { code?: string; mode?: AuthMode }) => {
|
||||
const { session } = useAuthStore()
|
||||
const [handshaking] = createSignal(false)
|
||||
const { getModal } = useModalStore()
|
||||
|
@ -45,13 +53,6 @@ export default (props: { code?: string; mode?: string }) => {
|
|||
let passElement: HTMLInputElement | undefined
|
||||
let codeElement: HTMLInputElement | undefined
|
||||
|
||||
// 3rd party provider auth handler
|
||||
const oauth = (provider: string): void => {
|
||||
const popup = window.open(`${baseUrl}/oauth/${provider}`, provider, 'width=740, height=420')
|
||||
popup?.focus()
|
||||
hideModal()
|
||||
}
|
||||
|
||||
// FIXME: restore logic
|
||||
// const usedEmails = {}
|
||||
// const checkEmailAsync = async (email: string) => {
|
||||
|
@ -185,9 +186,15 @@ export default (props: { code?: string; mode?: string }) => {
|
|||
{t('New stories every day and even more!')}
|
||||
</p>
|
||||
<p class="disclamer">
|
||||
{t('By signing up you agree with our')}
|
||||
<a href="/about/terms-of-use" onClick={hideModal}>
|
||||
{' ' + t('terms of use')}
|
||||
{t('By signing up you agree with our')}{' '}
|
||||
<a
|
||||
href="/about/terms-of-use"
|
||||
onClick={(event) => {
|
||||
hideModal()
|
||||
handleClientRouteLinkClick(event)
|
||||
}}
|
||||
>
|
||||
{t('terms of use')}
|
||||
</a>
|
||||
, {t('personal data usage and email notifications')}.
|
||||
</p>
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.mainHeaderInner {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.headerFixed.headerScrolledBottom,
|
||||
.headerFixed.headerScrolledTop {
|
||||
.mainLogo {
|
||||
|
@ -31,9 +35,26 @@
|
|||
}
|
||||
}
|
||||
|
||||
.popupShare {
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s;
|
||||
z-index: 1;
|
||||
|
||||
.headerScrolledTop & {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s, z-index 0s 0.3s;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
.headerFixed {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
||||
.fixed & {
|
||||
bottom: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.headerInner {
|
||||
|
@ -44,8 +65,7 @@
|
|||
justify-content: space-between;
|
||||
margin: 0;
|
||||
|
||||
&.fixed {
|
||||
border-bottom: 4px solid #000;
|
||||
.fixed & {
|
||||
left: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
|
@ -62,25 +82,33 @@
|
|||
.mainLogo {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
height: 70px;
|
||||
height: 56px;
|
||||
padding: 0 $container-padding-x 0 0;
|
||||
position: relative;
|
||||
transition: height 0.2s;
|
||||
text-align: center;
|
||||
z-index: 9;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
height: 70px;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 32px;
|
||||
height: 20px;
|
||||
object-fit: contain;
|
||||
object-position: left;
|
||||
transition: height 0.2s;
|
||||
vertical-align: middle;
|
||||
width: 100px;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
width: 175px;
|
||||
}
|
||||
|
@ -113,6 +141,14 @@
|
|||
padding-left: 0;
|
||||
position: relative;
|
||||
@include font-size(1.7rem);
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fixed & {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.mainNavigation {
|
||||
|
@ -145,7 +181,7 @@
|
|||
padding: divide($container-padding-x, 2);
|
||||
}
|
||||
|
||||
&.fixed {
|
||||
.fixed & {
|
||||
display: inline-flex;
|
||||
|
||||
@include media-breakpoint-down(lg) {
|
||||
|
@ -181,13 +217,9 @@
|
|||
display: inline-flex;
|
||||
float: right;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
padding-left: divide($container-padding-x, 2);
|
||||
width: 2.2rem;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
padding-left: divide($container-padding-x, 2);
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
display: none;
|
||||
}
|
||||
|
@ -235,7 +267,7 @@
|
|||
top: 0;
|
||||
}
|
||||
|
||||
&.fixed {
|
||||
.fixed & {
|
||||
> div {
|
||||
opacity: 0;
|
||||
transition: opacity 0s;
|
||||
|
@ -276,6 +308,10 @@
|
|||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.headerSearch {
|
||||
|
@ -305,6 +341,7 @@
|
|||
|
||||
.articleControls {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
|
|
|
@ -3,12 +3,14 @@ import Private from './Private'
|
|||
import Notifications from './Notifications'
|
||||
import { Icon } from './Icon'
|
||||
import { Modal } from './Modal'
|
||||
import { Popup } from './Popup'
|
||||
import AuthModal from './AuthModal'
|
||||
import { t } from '../../utils/intl'
|
||||
import { useModalStore, showModal, useWarningsStore } from '../../stores/ui'
|
||||
import {useModalStore, showModal, useWarningsStore, toggleModal} from '../../stores/ui'
|
||||
import { useAuthStore } from '../../stores/auth'
|
||||
import { handleClientRouteLinkClick, router, Routes, useRouter } from '../../stores/router'
|
||||
import styles from './Header.module.scss'
|
||||
import stylesPopup from './Popup.module.scss'
|
||||
import privateStyles from './Private.module.scss'
|
||||
import { getPagePath } from '@nanostores/router'
|
||||
import { clsx } from 'clsx'
|
||||
|
@ -46,11 +48,10 @@ export const Header = (props: Props) => {
|
|||
const toggleFixed = () => setFixed(!fixed())
|
||||
// effects
|
||||
createEffect(() => {
|
||||
if (fixed() || getModal()) {
|
||||
document.body.classList.add('fixed')
|
||||
} else {
|
||||
document.body.classList.remove('fixed')
|
||||
}
|
||||
const isFixed = fixed() || (getModal() && getModal() !== 'share');
|
||||
|
||||
document.body.classList.toggle('fixed', isFixed);
|
||||
document.body.classList.toggle(styles.fixed, isFixed);
|
||||
}, [fixed(), getModal()])
|
||||
|
||||
// derived
|
||||
|
@ -68,8 +69,6 @@ export const Header = (props: Props) => {
|
|||
onMount(() => {
|
||||
let scrollTop = window.scrollY
|
||||
|
||||
// window.console.log(props.title)
|
||||
|
||||
const handleScroll = () => {
|
||||
setIsScrollingBottom(window.scrollY > scrollTop)
|
||||
setIsScrolled(window.scrollY > 0)
|
||||
|
@ -95,7 +94,43 @@ export const Header = (props: Props) => {
|
|||
<Modal name="auth">
|
||||
<AuthModal />
|
||||
</Modal>
|
||||
<div class="wide-container">
|
||||
|
||||
<div class={clsx(styles.mainHeaderInner, 'wide-container')}>
|
||||
<Popup name="share" class={clsx(styles.popupShare, stylesPopup.popupShare)}>
|
||||
<ul class="nodash">
|
||||
<li>
|
||||
<a href="#">
|
||||
<Icon name="vk-white" class={stylesPopup.icon}/>
|
||||
VK
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">
|
||||
<Icon name="facebook-white" class={stylesPopup.icon}/>
|
||||
Facebook
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">
|
||||
<Icon name="twitter-white" class={stylesPopup.icon}/>
|
||||
Twitter
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">
|
||||
<Icon name="telegram-white" class={stylesPopup.icon}/>
|
||||
Telegram
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">
|
||||
<Icon name="link-white" class={stylesPopup.icon}/>
|
||||
{t('Copy link')}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</Popup>
|
||||
|
||||
<nav class={clsx(styles.headerInner, 'row')} classList={{ fixed: fixed() }}>
|
||||
<div class={clsx(styles.mainLogo, 'col-auto')}>
|
||||
<a href={getPagePath(router, 'home')} onClick={handleClientRouteLinkClick}>
|
||||
|
@ -113,7 +148,7 @@ export const Header = (props: Props) => {
|
|||
>
|
||||
<For each={resources}>
|
||||
{(r) => (
|
||||
<li classList={{ selected: r.route === getPage().route }}>
|
||||
<li classList={{ [styles.selected]: r.route === getPage().route }}>
|
||||
<a href={getPagePath(router, r.route, null)} onClick={handleClientRouteLinkClick}>
|
||||
{r.name}
|
||||
</a>
|
||||
|
@ -159,7 +194,9 @@ export const Header = (props: Props) => {
|
|||
</div>
|
||||
<Show when={props.title}>
|
||||
<div class={styles.articleControls}>
|
||||
<Icon name="share-outline" class={styles.icon} />
|
||||
<button onClick={() => {toggleModal('share')}}>
|
||||
<Icon name="share-outline" class={styles.icon}/>
|
||||
</button>
|
||||
<a href="#comments">
|
||||
<Icon name="comments-outline" class={styles.icon} />
|
||||
</a>
|
||||
|
|
46
src/components/Nav/Popup.module.scss
Normal file
46
src/components/Nav/Popup.module.scss
Normal file
|
@ -0,0 +1,46 @@
|
|||
.popup {
|
||||
background: #fff;
|
||||
border: 2px solid #000;
|
||||
|
||||
@include font-size(1.6rem);
|
||||
|
||||
padding: 2.4rem 2.4rem 2.4rem 1.6rem;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
|
||||
ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 1.6rem;
|
||||
padding-left: 3.6rem;
|
||||
position: relative;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
border: none;
|
||||
}
|
||||
|
||||
img {
|
||||
filter: invert(1);
|
||||
max-height: 2rem;
|
||||
max-width: 2rem;
|
||||
}
|
||||
|
||||
.icon {
|
||||
left: 1.5rem;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
.popupShare {
|
||||
right: 1em;
|
||||
top: 4.5rem;
|
||||
}
|
33
src/components/Nav/Popup.tsx
Normal file
33
src/components/Nav/Popup.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { createEffect, createSignal, onMount, Show } from 'solid-js'
|
||||
import style from './Popup.module.scss'
|
||||
import { hideModal, useModalStore } from '../../stores/ui'
|
||||
import {clsx} from 'clsx';
|
||||
|
||||
interface PopupProps {
|
||||
name: string
|
||||
children: any
|
||||
class?: string
|
||||
}
|
||||
|
||||
export const Popup = (props: PopupProps) => {
|
||||
const { getModal } = useModalStore()
|
||||
|
||||
onMount(() => {
|
||||
window.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') hideModal()
|
||||
})
|
||||
})
|
||||
|
||||
const [visible, setVisible] = createSignal(false)
|
||||
createEffect(() => {
|
||||
setVisible(getModal() === props.name)
|
||||
})
|
||||
|
||||
return (
|
||||
<Show when={visible()}>
|
||||
<div class={clsx(style.popup, props.class)}>
|
||||
{props.children}
|
||||
</div>
|
||||
</Show>
|
||||
)
|
||||
}
|
|
@ -18,11 +18,6 @@ export default () => {
|
|||
<Icon name="pencil" />
|
||||
</a>
|
||||
</div>
|
||||
<div class={clsx(styles.userControlItem, styles.userControlItemSearch)}>
|
||||
<a href="/search">
|
||||
<Icon name="search" />
|
||||
</a>
|
||||
</div>
|
||||
<div class={clsx(styles.userControlItem, styles.userControlItemInbox)}>
|
||||
<a href="/inbox">
|
||||
{/*FIXME: replace with route*/}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { AllAuthorsView } from '../Views/AllAuthors'
|
|||
import type { PageProps } from '../types'
|
||||
import { createSignal, onMount, Show } from 'solid-js'
|
||||
import { loadAllAuthors } from '../../stores/zine/authors'
|
||||
import { t } from '../../utils/intl'
|
||||
import { Loading } from '../Loading'
|
||||
|
||||
export const AllAuthorsPage = (props: PageProps) => {
|
||||
const [isLoaded, setIsLoaded] = createSignal<boolean>(Boolean(props.allAuthors))
|
||||
|
@ -19,7 +19,7 @@ export const AllAuthorsPage = (props: PageProps) => {
|
|||
|
||||
return (
|
||||
<MainLayout>
|
||||
<Show when={isLoaded()} fallback={t('Loading')}>
|
||||
<Show when={isLoaded()} fallback={<Loading />}>
|
||||
<AllAuthorsView authors={props.allAuthors} />
|
||||
</Show>
|
||||
</MainLayout>
|
||||
|
|
|
@ -2,8 +2,8 @@ import { MainLayout } from '../Layouts/MainLayout'
|
|||
import { AllTopicsView } from '../Views/AllTopics'
|
||||
import type { PageProps } from '../types'
|
||||
import { createSignal, onMount, Show } from 'solid-js'
|
||||
import { t } from '../../utils/intl'
|
||||
import { loadAllTopics } from '../../stores/zine/topics'
|
||||
import { Loading } from '../Loading'
|
||||
|
||||
export const AllTopicsPage = (props: PageProps) => {
|
||||
const [isLoaded, setIsLoaded] = createSignal<boolean>(Boolean(props.allTopics))
|
||||
|
@ -19,7 +19,7 @@ export const AllTopicsPage = (props: PageProps) => {
|
|||
|
||||
return (
|
||||
<MainLayout>
|
||||
<Show when={isLoaded()} fallback={t('Loading')}>
|
||||
<Show when={isLoaded()} fallback={<Loading />}>
|
||||
<AllTopicsView topics={props.allTopics} />
|
||||
</Show>
|
||||
</MainLayout>
|
||||
|
|
|
@ -3,9 +3,9 @@ import { ArticleView } from '../Views/Article'
|
|||
import type { PageProps } from '../types'
|
||||
import { loadArticle, useArticlesStore } from '../../stores/zine/articles'
|
||||
import { createMemo, onMount, Show } from 'solid-js'
|
||||
import { t } from '../../utils/intl'
|
||||
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] : []
|
||||
|
@ -38,7 +38,7 @@ export const ArticlePage = (props: PageProps) => {
|
|||
|
||||
return (
|
||||
<MainLayout headerTitle={article()?.title || ''}>
|
||||
<Show when={Boolean(article())} fallback={t('Loading')}>
|
||||
<Show when={Boolean(article())} fallback={<Loading />}>
|
||||
<ArticleView article={article()} />
|
||||
</Show>
|
||||
</MainLayout>
|
||||
|
|
|
@ -4,8 +4,8 @@ import type { PageProps } from '../types'
|
|||
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||
import { loadArticlesForAuthors, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { useRouter } from '../../stores/router'
|
||||
import { t } from '../../utils/intl'
|
||||
import { loadAuthor } from '../../stores/zine/authors'
|
||||
import { Loading } from '../Loading'
|
||||
|
||||
export const AuthorPage = (props: PageProps) => {
|
||||
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.authorArticles) && Boolean(props.author))
|
||||
|
@ -37,7 +37,7 @@ export const AuthorPage = (props: PageProps) => {
|
|||
|
||||
return (
|
||||
<MainLayout>
|
||||
<Show when={isLoaded()} fallback={t('Loading')}>
|
||||
<Show when={isLoaded()} fallback={<Loading />}>
|
||||
<AuthorView author={props.author} authorArticles={props.authorArticles} authorSlug={slug()} />
|
||||
</Show>
|
||||
</MainLayout>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { FeedView } from '../Views/Feed'
|
|||
import type { PageProps } from '../types'
|
||||
import { createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||
import { loadRecentArticles, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { t } from '../../utils/intl'
|
||||
import { Loading } from '../Loading'
|
||||
|
||||
export const FeedPage = (props: PageProps) => {
|
||||
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.feedArticles))
|
||||
|
@ -22,7 +22,7 @@ export const FeedPage = (props: PageProps) => {
|
|||
|
||||
return (
|
||||
<MainLayout>
|
||||
<Show when={isLoaded()} fallback={t('Loading')}>
|
||||
<Show when={isLoaded()} fallback={<Loading />}>
|
||||
<FeedView articles={props.feedArticles} />
|
||||
</Show>
|
||||
</MainLayout>
|
||||
|
|
|
@ -2,9 +2,9 @@ import { HomeView } from '../Views/Home'
|
|||
import { MainLayout } from '../Layouts/MainLayout'
|
||||
import type { PageProps } from '../types'
|
||||
import { createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||
import { t } from '../../utils/intl'
|
||||
import { loadPublishedArticles, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { loadRandomTopics } from '../../stores/zine/topics'
|
||||
import { Loading } from '../Loading'
|
||||
|
||||
export const HomePage = (props: PageProps) => {
|
||||
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.homeArticles) && Boolean(props.randomTopics))
|
||||
|
@ -24,7 +24,7 @@ export const HomePage = (props: PageProps) => {
|
|||
|
||||
return (
|
||||
<MainLayout>
|
||||
<Show when={isLoaded()} fallback={t('Loading')}>
|
||||
<Show when={isLoaded()} fallback={<Loading />}>
|
||||
<HomeView randomTopics={props.randomTopics} recentPublishedArticles={props.homeArticles || []} />
|
||||
</Show>
|
||||
</MainLayout>
|
||||
|
|
|
@ -3,8 +3,8 @@ 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 { t } from '../../utils/intl'
|
||||
import { useRouter } from '../../stores/router'
|
||||
import { Loading } from '../Loading'
|
||||
|
||||
export const SearchPage = (props: PageProps) => {
|
||||
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.searchResults))
|
||||
|
@ -34,7 +34,7 @@ export const SearchPage = (props: PageProps) => {
|
|||
|
||||
return (
|
||||
<MainLayout>
|
||||
<Show when={isLoaded()} fallback={t('Loading')}>
|
||||
<Show when={isLoaded()} fallback={<Loading />}>
|
||||
<SearchView results={props.searchResults || []} query={props.searchQuery} />
|
||||
</Show>
|
||||
</MainLayout>
|
||||
|
|
|
@ -4,8 +4,8 @@ import type { PageProps } from '../types'
|
|||
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||
import { loadArticlesForTopics, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { useRouter } from '../../stores/router'
|
||||
import { t } from '../../utils/intl'
|
||||
import { loadTopic } from '../../stores/zine/topics'
|
||||
import { Loading } from '../Loading'
|
||||
|
||||
export const TopicPage = (props: PageProps) => {
|
||||
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.authorArticles) && Boolean(props.author))
|
||||
|
@ -37,7 +37,7 @@ export const TopicPage = (props: PageProps) => {
|
|||
|
||||
return (
|
||||
<MainLayout>
|
||||
<Show when={isLoaded()} fallback={t('Loading')}>
|
||||
<Show when={isLoaded()} fallback={<Loading />}>
|
||||
<TopicView topic={props.topic} topicArticles={props.topicArticles} topicSlug={slug()} />
|
||||
</Show>
|
||||
</MainLayout>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
margin-left: 0;
|
||||
}
|
||||
|
||||
.topic-details__item {
|
||||
.topicDetailsItem {
|
||||
margin-bottom: 1.2rem;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
|
@ -17,14 +17,28 @@
|
|||
}
|
||||
}
|
||||
|
||||
.topic-title {
|
||||
.topicInRow {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
button {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.topicTitle {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.topicTitle {
|
||||
font-weight: bold;
|
||||
@include font-size(1.7rem);
|
||||
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
.topic__avatar {
|
||||
.topicAvatar {
|
||||
border-radius: 100%;
|
||||
height: 64px;
|
||||
margin-right: 1.2rem;
|
||||
|
@ -41,7 +55,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.topic-description {
|
||||
.topicDescription {
|
||||
@include font-size(1.5rem);
|
||||
|
||||
color: #696969;
|
||||
|
@ -52,14 +66,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
.topic-description--short {
|
||||
display: box;
|
||||
.topicDescriptionShort {
|
||||
display: block;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.topic-details {
|
||||
.topicDetails {
|
||||
@include font-size(1.7rem);
|
||||
|
||||
color: #9fa1a7;
|
||||
|
@ -70,7 +84,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.topic-details__item {
|
||||
.topicDetailsItem {
|
||||
margin-right: 1.6rem;
|
||||
white-space: nowrap;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { capitalize, plural } from '../../utils'
|
||||
import { Show } from 'solid-js/web'
|
||||
import './Card.scss'
|
||||
import style from './Card.module.scss'
|
||||
import { createMemo } from 'solid-js'
|
||||
import type { Topic } from '../../graphql/types.gen'
|
||||
import { FollowingEntity } from '../../graphql/types.gen'
|
||||
|
@ -15,6 +15,9 @@ interface TopicProps {
|
|||
subscribed?: boolean
|
||||
shortDescription?: boolean
|
||||
subscribeButtonBottom?: boolean
|
||||
additionalClass?: string
|
||||
isTopicInRow?: boolean
|
||||
iconButton?: boolean
|
||||
}
|
||||
|
||||
export const TopicCard = (props: TopicProps) => {
|
||||
|
@ -37,15 +40,21 @@ export const TopicCard = (props: TopicProps) => {
|
|||
}
|
||||
}
|
||||
return (
|
||||
<div class="topic" classList={{ row: !props.compact && !props.subscribeButtonBottom }}>
|
||||
<div
|
||||
class={style.topic}
|
||||
classList={{
|
||||
row: !props.compact && !props.subscribeButtonBottom,
|
||||
[style.topicInRow]: props.isTopicInRow
|
||||
}}
|
||||
>
|
||||
<div classList={{ 'col-md-7': !props.compact && !props.subscribeButtonBottom }}>
|
||||
<Show when={props.topic.title}>
|
||||
<div class="topic-title">
|
||||
<div class={style.topicTitle}>
|
||||
<a href={`/topic/${props.topic.slug}`}>{capitalize(props.topic.title || '')}</a>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={props.topic.pic}>
|
||||
<div class="topic__avatar">
|
||||
<div class={style.topicAvatar}>
|
||||
<a href={props.topic.slug}>
|
||||
<img src={props.topic.pic} alt={props.topic.title} />
|
||||
</a>
|
||||
|
@ -53,15 +62,18 @@ export const TopicCard = (props: TopicProps) => {
|
|||
</Show>
|
||||
|
||||
<Show when={!props.compact && props.topic?.body}>
|
||||
<div class="topic-description" classList={{ 'topic-description--short': props.shortDescription }}>
|
||||
<div
|
||||
class={style.topicDescription}
|
||||
classList={{ 'topic-description--short': props.shortDescription }}
|
||||
>
|
||||
{props.topic.body}
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={props.topic?.stat}>
|
||||
<div class="topic-details">
|
||||
<div class={style.topicDetails}>
|
||||
<Show when={!props.compact}>
|
||||
<span class="topic-details__item" classList={{ compact: props.compact }}>
|
||||
<span class={style.topicDetailsTtem} classList={{ compact: props.compact }}>
|
||||
{props.topic.stat?.shouts +
|
||||
' ' +
|
||||
t('post') +
|
||||
|
@ -70,7 +82,7 @@ export const TopicCard = (props: TopicProps) => {
|
|||
locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']
|
||||
)}
|
||||
</span>
|
||||
<span class="topic-details__item" classList={{ compact: props.compact }}>
|
||||
<span class={style.topicDetailsTtem} classList={{ compact: props.compact }}>
|
||||
{props.topic.stat?.authors +
|
||||
' ' +
|
||||
t('author') +
|
||||
|
@ -79,7 +91,7 @@ export const TopicCard = (props: TopicProps) => {
|
|||
locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']
|
||||
)}
|
||||
</span>
|
||||
<span class="topic-details__item" classList={{ compact: props.compact }}>
|
||||
<span class={style.topicDetailsItem} classList={{ compact: props.compact }}>
|
||||
{props.topic.stat?.followers +
|
||||
' ' +
|
||||
t('follower') +
|
||||
|
@ -118,12 +130,16 @@ export const TopicCard = (props: TopicProps) => {
|
|||
when={subscribed()}
|
||||
fallback={
|
||||
<button onClick={() => subscribe(true)} class="button--light">
|
||||
+ {t('Follow')}
|
||||
<Show when={props.iconButton}>{/*<Icon name={}/>*/}</Show>
|
||||
|
||||
<Show when={!props.iconButton}>+ {t('Follow')}</Show>
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<button onClick={() => subscribe(false)} class="button--light">
|
||||
- {t('Unfollow')}
|
||||
<Show when={props.iconButton}>{/*<Icon name={}/>*/}</Show>
|
||||
|
||||
<Show when={!props.iconButton}>- {t('Unfollow')}</Show>
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
|
|
|
@ -31,6 +31,11 @@ export const AllAuthorsView = (props: Props) => {
|
|||
|
||||
const byLetter = createMemo<{ [letter: string]: Author[] }>(() => {
|
||||
return sortedAuthors().reduce((acc, author) => {
|
||||
if (!author.name) {
|
||||
// name === null for new users
|
||||
return acc
|
||||
}
|
||||
|
||||
const letter = author.name[0].toUpperCase()
|
||||
if (!acc[letter]) {
|
||||
acc[letter] = []
|
||||
|
|
|
@ -9,8 +9,9 @@ import { useAuthorsStore } from '../../stores/zine/authors'
|
|||
import { useArticlesStore } from '../../stores/zine/articles'
|
||||
|
||||
import '../../styles/Topic.scss'
|
||||
// import { useTopicsStore } from '../../stores/zine/topics'
|
||||
import { useTopicsStore } from '../../stores/zine/topics'
|
||||
import { useRouter } from '../../stores/router'
|
||||
import Beside from '../Feed/Beside'
|
||||
|
||||
// TODO: load reactions on client
|
||||
type AuthorProps = {
|
||||
|
@ -30,6 +31,7 @@ export const AuthorView = (props: AuthorProps) => {
|
|||
sortedArticles: props.authorArticles
|
||||
})
|
||||
const { authorEntities } = useAuthorsStore({ authors: [props.author] })
|
||||
const { topicsByAuthor } = useTopicsStore()
|
||||
|
||||
const author = createMemo(() => authorEntities()[props.authorSlug])
|
||||
const { getSearchParams, changeSearchParam } = useRouter<AuthorPageSearchParams>()
|
||||
|
@ -54,21 +56,22 @@ export const AuthorView = (props: AuthorProps) => {
|
|||
{t('Recent')}
|
||||
</button>
|
||||
</li>
|
||||
<li classList={{ selected: getSearchParams().by === 'rating' }}>
|
||||
<button type="button" onClick={() => changeSearchParam('by', 'rating')}>
|
||||
{t('Popular')}
|
||||
</button>
|
||||
</li>
|
||||
<li classList={{ selected: getSearchParams().by === 'viewed' }}>
|
||||
<button type="button" onClick={() => changeSearchParam('by', 'viewed')}>
|
||||
{t('Views')}
|
||||
</button>
|
||||
</li>
|
||||
<li classList={{ selected: getSearchParams().by === 'commented' }}>
|
||||
<button type="button" onClick={() => changeSearchParam('by', 'commented')}>
|
||||
{t('Discussing')}
|
||||
</button>
|
||||
</li>
|
||||
{/*TODO: server sort*/}
|
||||
{/*<li classList={{ selected: getSearchParams().by === 'rating' }}>*/}
|
||||
{/* <button type="button" onClick={() => changeSearchParam('by', 'rating')}>*/}
|
||||
{/* {t('Popular')}*/}
|
||||
{/* </button>*/}
|
||||
{/*</li>*/}
|
||||
{/*<li classList={{ selected: getSearchParams().by === 'viewed' }}>*/}
|
||||
{/* <button type="button" onClick={() => changeSearchParam('by', 'viewed')}>*/}
|
||||
{/* {t('Views')}*/}
|
||||
{/* </button>*/}
|
||||
{/*</li>*/}
|
||||
{/*<li classList={{ selected: getSearchParams().by === 'commented' }}>*/}
|
||||
{/* <button type="button" onClick={() => changeSearchParam('by', 'commented')}>*/}
|
||||
{/* {t('Discussing')}*/}
|
||||
{/* </button>*/}
|
||||
{/*</li>*/}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
|
@ -79,24 +82,33 @@ export const AuthorView = (props: AuthorProps) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="floor">
|
||||
<h3 class="col-12">{title()}</h3>
|
||||
<div class="row">
|
||||
<Show when={sortedArticles().length > 0}>
|
||||
{/*FIXME*/}
|
||||
{/*<Beside*/}
|
||||
{/* title={t('Topics which supported by author')}*/}
|
||||
{/* values={getTopicsByAuthor()[author().slug].slice(0, 5)}*/}
|
||||
{/* beside={articles()[0]}*/}
|
||||
{/* wrapper={'topic'}*/}
|
||||
{/* topicShortDescription={true}*/}
|
||||
{/*/>*/}
|
||||
<Row3 articles={sortedArticles().slice(1, 4)} />
|
||||
<h3 class="col-12">{title()}</h3>
|
||||
<div class="row">
|
||||
<Show when={sortedArticles().length > 0}>
|
||||
<Beside
|
||||
title={t('Topics which supported by author')}
|
||||
values={topicsByAuthor()[author().slug].slice(0, 5)}
|
||||
beside={sortedArticles()[0]}
|
||||
wrapper={'topic'}
|
||||
topicShortDescription={true}
|
||||
isTopicCompact={true}
|
||||
isTopicInRow={true}
|
||||
iconButton={true}
|
||||
/>
|
||||
<Row3 articles={sortedArticles().slice(1, 4)} />
|
||||
|
||||
<Show when={sortedArticles().length > 4}>
|
||||
<Row2 articles={sortedArticles().slice(4, 6)} />
|
||||
</Show>
|
||||
|
||||
<Show when={sortedArticles().length > 6}>
|
||||
<Row3 articles={sortedArticles().slice(6, 9)} />
|
||||
</Show>
|
||||
|
||||
<Show when={sortedArticles().length > 9}>
|
||||
<Row3 articles={sortedArticles().slice(9, 12)} />
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
|
|
@ -32,14 +32,6 @@ export const TopicView = (props: TopicProps) => {
|
|||
|
||||
const topic = createMemo(() => topicEntities()[props.topicSlug])
|
||||
|
||||
/*
|
||||
const slug = createMemo<string>(() => {
|
||||
let slug = props?.slug
|
||||
if (props?.slug.startsWith('@')) slug = slug.replace('@', '')
|
||||
return slug
|
||||
})
|
||||
*/
|
||||
|
||||
const title = createMemo(() => {
|
||||
const m = getSearchParams().by
|
||||
if (m === 'viewed') return t('Top viewed')
|
||||
|
@ -60,21 +52,22 @@ export const TopicView = (props: TopicProps) => {
|
|||
{t('Recent')}
|
||||
</button>
|
||||
</li>
|
||||
<li classList={{ selected: getSearchParams().by === 'rating' }}>
|
||||
<button type="button" onClick={() => changeSearchParam('by', 'rating')}>
|
||||
{t('Popular')}
|
||||
</button>
|
||||
</li>
|
||||
<li classList={{ selected: getSearchParams().by === 'viewed' }}>
|
||||
<button type="button" onClick={() => changeSearchParam('by', 'viewed')}>
|
||||
{t('Views')}
|
||||
</button>
|
||||
</li>
|
||||
<li classList={{ selected: getSearchParams().by === 'commented' }}>
|
||||
<button type="button" onClick={() => changeSearchParam('by', 'commented')}>
|
||||
{t('Discussing')}
|
||||
</button>
|
||||
</li>
|
||||
{/*TODO: server sort*/}
|
||||
{/*<li classList={{ selected: getSearchParams().by === 'rating' }}>*/}
|
||||
{/* <button type="button" onClick={() => changeSearchParam('by', 'rating')}>*/}
|
||||
{/* {t('Popular')}*/}
|
||||
{/* </button>*/}
|
||||
{/*</li>*/}
|
||||
{/*<li classList={{ selected: getSearchParams().by === 'viewed' }}>*/}
|
||||
{/* <button type="button" onClick={() => changeSearchParam('by', 'viewed')}>*/}
|
||||
{/* {t('Views')}*/}
|
||||
{/* </button>*/}
|
||||
{/*</li>*/}
|
||||
{/*<li classList={{ selected: getSearchParams().by === 'commented' }}>*/}
|
||||
{/* <button type="button" onClick={() => changeSearchParam('by', 'commented')}>*/}
|
||||
{/* {t('Discussing')}*/}
|
||||
{/* </button>*/}
|
||||
{/*</li>*/}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
|
@ -92,7 +85,10 @@ export const TopicView = (props: TopicProps) => {
|
|||
<For each={sortedArticles().slice(0, 6)}>
|
||||
{(article) => (
|
||||
<div class="col-md-6">
|
||||
<ArticleCard article={article} />
|
||||
<ArticleCard
|
||||
article={article}
|
||||
settings={{ isFloorImportant: true, isBigTitle: true }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
|
@ -108,11 +104,11 @@ export const TopicView = (props: TopicProps) => {
|
|||
beside={sortedArticles()[6]}
|
||||
wrapper={'author'}
|
||||
/>
|
||||
<Row3 articles={sortedArticles().slice(6, 9)} />
|
||||
<Row2 articles={sortedArticles().slice(9, 11)} />
|
||||
<Row3 articles={sortedArticles().slice(11, 14)} />
|
||||
<Row3 articles={sortedArticles().slice(14, 17)} />
|
||||
<Row3 articles={sortedArticles().slice(17, 20)} />
|
||||
<Row3 articles={sortedArticles().slice(7, 10)} />
|
||||
<Row2 articles={sortedArticles().slice(10, 12)} />
|
||||
<Row3 articles={sortedArticles().slice(12, 15)} />
|
||||
<Row3 articles={sortedArticles().slice(15, 18)} />
|
||||
<Row3 articles={sortedArticles().slice(18, 21)} />
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"Comments": "Комментарии",
|
||||
"Communities": "Сообщества",
|
||||
"Create account": "Создать аккаунт",
|
||||
"Copy link": "Скопировать ссылку",
|
||||
"Delete": "Удалить",
|
||||
"Discours": "Дискурс",
|
||||
"Discours is an intellectual environment, a web space and tools that allows authors to collaborate with readers and come together to co-create publications and media projects": "Дискурс — это интеллектуальная среда, веб-пространство и инструменты, которые позволяют авторам сотрудничать с читателями и объединяться для совместного создания публикаций и медиапроектов",
|
||||
|
|
|
@ -19,8 +19,9 @@ const modal = atom<ModalType>(null)
|
|||
const warnings = atom<Warning[]>([])
|
||||
|
||||
export const showModal = (modalType: ModalType) => modal.set(modalType)
|
||||
|
||||
export const hideModal = () => modal.set(null)
|
||||
export const toggleModal = (modalType) => modal.get() ? hideModal() : showModal(modalType)
|
||||
|
||||
export const clearWarns = () => warnings.set([])
|
||||
export const warn = (warning: Warning) => warnings.set([...warnings.get(), warning])
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ body {
|
|||
&.fixed {
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,7 +211,7 @@ button {
|
|||
|
||||
font-weight: 400;
|
||||
margin-top: 0.6rem;
|
||||
padding: 0.6rem 1.2rem 1rem 1rem;
|
||||
padding: 0.6rem 1.2rem 0.6rem 1rem;
|
||||
}
|
||||
|
||||
form {
|
||||
|
@ -460,29 +461,48 @@ figcaption {
|
|||
}
|
||||
}
|
||||
|
||||
.floor--7 {
|
||||
@include media-breakpoint-down(md) {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.col-md-6 {
|
||||
margin-bottom: 1.6em;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.floor--important {
|
||||
background: #000;
|
||||
color: #fff;
|
||||
padding: $grid-gutter-width 0;
|
||||
padding-bottom: $container-padding-x;
|
||||
padding-top: $container-padding-x;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
padding-bottom: $grid-gutter-width;
|
||||
padding-top: $grid-gutter-width;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@include font-size(4.4rem);
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
margin-bottom: 5rem;
|
||||
}
|
||||
|
||||
.all-materials,
|
||||
.shout-card__title {
|
||||
.all-materials {
|
||||
a {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.shout__topic,
|
||||
.shout__author {
|
||||
a {
|
||||
color: rgb(255 255 255 / 50%);
|
||||
}
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background: #fff;
|
||||
color: #000 !important;
|
||||
|
@ -503,25 +523,6 @@ figcaption {
|
|||
}
|
||||
}
|
||||
|
||||
.shout__topic {
|
||||
font-size: 1.2rem;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
a {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: $link-color;
|
||||
|
||||
&:hover {
|
||||
background: $link-color;
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line */
|
||||
astro-island {
|
||||
display: flex !important;
|
||||
|
@ -533,7 +534,9 @@ astro-island {
|
|||
|
||||
.main-content {
|
||||
flex: 1 100%;
|
||||
min-height: 300px;
|
||||
padding-top: 100px;
|
||||
position: relative;
|
||||
transition: all 1s ease;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user