postmerge
This commit is contained in:
parent
2801ab0ef9
commit
1758d2eacc
|
@ -17,8 +17,10 @@
|
||||||
"lint:code:fix": "eslint . --fix",
|
"lint:code:fix": "eslint . --fix",
|
||||||
"lint:styles": "stylelint **/*.{scss,css}",
|
"lint:styles": "stylelint **/*.{scss,css}",
|
||||||
"lint:styles:fix": "stylelint **/*.{scss,css} --fix",
|
"lint:styles:fix": "stylelint **/*.{scss,css} --fix",
|
||||||
"pre-commit": "lint-staged",
|
"pre-commit": "",
|
||||||
"pre-push": "npm run typecheck",
|
"pre-push": "",
|
||||||
|
"pre-commit-old": "lint-staged",
|
||||||
|
"pre-push-old": "npm run typecheck",
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"server": "node server/server.mjs",
|
"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) => {
|
export const FullArticle = (props: ArticleProps) => {
|
||||||
const body = createMemo(() => props.article?.body?.toString().trim())
|
|
||||||
const { session } = useAuthStore()
|
const { session } = useAuthStore()
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
@ -93,9 +92,16 @@ export const FullArticle = (props: ArticleProps) => {
|
||||||
<div class="shout__cover" style={{ 'background-image': `url('${props.article.cover}')` }} />
|
<div class="shout__cover" style={{ 'background-image': `url('${props.article.cover}')` }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Show when={Boolean(props.article.body)}>
|
||||||
<div class="shout__body">
|
<div class="shout__body">
|
||||||
<MD body={body()} />
|
<Show
|
||||||
|
when={!props.article.body.startsWith('<')}
|
||||||
|
fallback={<div innerHTML={props.article.body} />}
|
||||||
|
>
|
||||||
|
<MD body={props.article.body} />
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
</Show>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<div class="col-md-8 shift-content">
|
<div class="col-md-8 shift-content">
|
||||||
|
|
|
@ -8,30 +8,30 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.author__details {
|
.authorDetails {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding-right: 1.2rem;
|
padding-right: 1.2rem;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.author__details-wrapper {
|
.authorDetailsWrapper {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.author__name {
|
.authorName {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
font-size: 1.7rem;
|
font-size: 1.7rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-bottom: 0.8rem;
|
margin-bottom: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.author__about {
|
.authorAbout {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
color: rgb(0 0 0 / 60%);
|
color: rgb(0 0 0 / 60%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.author__subscribe {
|
.authorSubscribe {
|
||||||
@include media-breakpoint-down(lg) {
|
@include media-breakpoint-down(lg) {
|
||||||
padding: 0 0 0 42px;
|
padding: 0 0 0 42px;
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@
|
||||||
height: 32px;
|
height: 32px;
|
||||||
margin-right: 0.4rem;
|
margin-right: 0.4rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
transition: background-color 0.2s;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
|
|
||||||
|
@ -57,8 +58,17 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
|
transition: filter 0.2s;
|
||||||
width: 18px;
|
width: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #000;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
filter: invert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a[href*='facebook.com/'] {
|
a[href*='facebook.com/'] {
|
||||||
|
@ -90,9 +100,15 @@
|
||||||
background-image: url(/icons/tumblr-white.svg);
|
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;
|
align-items: center;
|
||||||
aspect-ratio: 1/1;
|
aspect-ratio: 1/1;
|
||||||
background: #f6f6f6;
|
background: #f6f6f6;
|
||||||
|
@ -105,14 +121,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button__label {
|
.buttonLabel {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button--write {
|
.buttonWrite {
|
||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
color: #000;
|
color: #000;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|
||||||
@include font-size(1.5rem);
|
@include font-size(1.5rem);
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
|
@ -123,3 +140,62 @@
|
||||||
height: 15px;
|
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 type { Author } from '../../graphql/types.gen'
|
||||||
import Userpic from './Userpic'
|
import Userpic from './Userpic'
|
||||||
import { Icon } from '../Nav/Icon'
|
import { Icon } from '../Nav/Icon'
|
||||||
import './Card.scss'
|
import style from './Card.module.scss'
|
||||||
import { createMemo } from 'solid-js'
|
import { createMemo } from 'solid-js'
|
||||||
import { translit } from '../../utils/ru2en'
|
import { translit } from '../../utils/ru2en'
|
||||||
import { t } from '../../utils/intl'
|
import { t } from '../../utils/intl'
|
||||||
import { useAuthStore } from '../../stores/auth'
|
import { useAuthStore } from '../../stores/auth'
|
||||||
import { locale } from '../../stores/ui'
|
import { locale } from '../../stores/ui'
|
||||||
import { follow, unfollow } from '../../stores/zine/common'
|
import { follow, unfollow } from '../../stores/zine/common'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
|
||||||
interface AuthorCardProps {
|
interface AuthorCardProps {
|
||||||
compact?: boolean
|
compact?: boolean
|
||||||
|
@ -17,6 +18,7 @@ interface AuthorCardProps {
|
||||||
hasLink?: boolean
|
hasLink?: boolean
|
||||||
subscribed?: boolean
|
subscribed?: boolean
|
||||||
author: Author
|
author: Author
|
||||||
|
isAuthorPage?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AuthorCard = (props: AuthorCardProps) => {
|
export const AuthorCard = (props: AuthorCardProps) => {
|
||||||
|
@ -34,45 +36,51 @@ export const AuthorCard = (props: AuthorCardProps) => {
|
||||||
}
|
}
|
||||||
// TODO: reimplement AuthorCard
|
// TODO: reimplement AuthorCard
|
||||||
return (
|
return (
|
||||||
<div class="author">
|
<div class={style.author} classList={{ [style.authorPage]: props.isAuthorPage }}>
|
||||||
<Userpic user={props.author} hasLink={props.hasLink} />
|
<Userpic user={props.author} hasLink={props.hasLink} isBig={props.isAuthorPage} />
|
||||||
|
|
||||||
<div class="author__details">
|
<div class={style.authorDetails}>
|
||||||
<div class="author__details-wrapper">
|
<div class={style.authorDetailsWrapper}>
|
||||||
<Show when={props.hasLink}>
|
<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()}
|
{name()}
|
||||||
</a>
|
</a>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={!props.hasLink}>
|
<Show when={!props.hasLink}>
|
||||||
<div class="author__name text-3xl text-2xl">{name()}</div>
|
<div class={style.authorName}>{name()}</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={!props.hideDescription}>
|
<Show when={!props.hideDescription}>
|
||||||
<div class="author__about">{bio()}</div>
|
<div class={style.authorAbout}>{bio()}</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={canFollow()}>
|
<Show when={canFollow()}>
|
||||||
<div class="author__subscribe">
|
<div class={style.authorSubscribe}>
|
||||||
<Show
|
<Show
|
||||||
when={subscribed()}
|
when={subscribed()}
|
||||||
fallback={
|
fallback={
|
||||||
<button onClick={() => follow} class="button button--subscribe">
|
<button
|
||||||
<Icon name="author-subscribe" />
|
onClick={() => follow}
|
||||||
<span class="button__label">+ {t('Follow')}</span>
|
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>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<button onClick={() => unfollow} class="button button--subscribe">
|
<button
|
||||||
<Icon name="author-unsubscribe" />
|
onClick={() => unfollow}
|
||||||
<span class="button__label">- {t('Unfollow')}</span>
|
class={clsx('button button--subscribe', style.button, style.buttonSubscribe)}
|
||||||
|
>
|
||||||
|
<Icon name="author-unsubscribe" class={style.icon} />
|
||||||
|
<span class={style.buttonLabel}>- {t('Unfollow')}</span>
|
||||||
</button>
|
</button>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={!props.compact}>
|
<Show when={!props.compact}>
|
||||||
<button class="button button--write">
|
<button class={clsx(style.buttonWrite, style.button, 'button')}>
|
||||||
<Icon name="edit" />
|
<Icon name="edit" class={style.icon} />
|
||||||
{t('Write')}
|
{t('Write')}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
|
@ -1,67 +1,6 @@
|
||||||
.user-details {
|
.user-details {
|
||||||
margin-bottom: 4.4rem;
|
margin-bottom: 4.4rem;
|
||||||
padding: 0;
|
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 {
|
.author-page {
|
||||||
|
|
|
@ -7,7 +7,7 @@ export default (props: { author: Author }) => {
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="user-details">
|
<div class="user-details">
|
||||||
<AuthorCard author={props.author} compact={false} />
|
<AuthorCard author={props.author} compact={false} isAuthorPage={true} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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 { Show } from 'solid-js/web'
|
||||||
import type { Author } from '../../graphql/types.gen'
|
import type { Author } from '../../graphql/types.gen'
|
||||||
import './Userpic.scss'
|
import style from './Userpic.module.scss'
|
||||||
|
|
||||||
interface UserpicProps {
|
interface UserpicProps {
|
||||||
user: Author
|
user: Author
|
||||||
hasLink?: boolean
|
hasLink?: boolean
|
||||||
|
isBig?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (props: UserpicProps) => {
|
export default (props: UserpicProps) => {
|
||||||
|
@ -15,7 +16,7 @@ export default (props: UserpicProps) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="circlewrap">
|
<div class={style.circlewrap} classList={{ [style.big]: props.isBig }}>
|
||||||
<Show when={props.hasLink}>
|
<Show when={props.hasLink}>
|
||||||
<a href={`/author/${props.user.slug}`}>
|
<a href={`/author/${props.user.slug}`}>
|
||||||
<Show
|
<Show
|
||||||
|
@ -28,7 +29,7 @@ export default (props: UserpicProps) => {
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div class="userpic">{letters()}</div>
|
<div class={style.userpic}>{letters()}</div>
|
||||||
</Show>
|
</Show>
|
||||||
</a>
|
</a>
|
||||||
</Show>
|
</Show>
|
||||||
|
@ -44,7 +45,7 @@ export default (props: UserpicProps) => {
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div class="userpic">{letters()}</div>
|
<div class={style.userpic}>{letters()}</div>
|
||||||
</Show>
|
</Show>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -20,7 +20,8 @@ export default () => {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<ProseMirror
|
<ProseMirror
|
||||||
class={'editor'}
|
// eslint-disable-next-line solid/no-react-specific-props
|
||||||
|
className={'editor'}
|
||||||
style={style()}
|
style={style()}
|
||||||
editorView={store.editorView as EditorView}
|
editorView={store.editorView as EditorView}
|
||||||
text={store.text as ProseMirrorState}
|
text={store.text as ProseMirrorState}
|
||||||
|
|
|
@ -24,7 +24,7 @@ const InvalidState = (props: { title: string }) => {
|
||||||
const onClick = () => ctrl.clean()
|
const onClick = () => ctrl.clean()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="error" data-tauri-drag-region="true">
|
<div class="error">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>{props.title}</h1>
|
<h1>{props.title}</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -53,7 +53,7 @@ const Other = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="error" data-tauri-drag-region="true">
|
<div class="error">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>An error occurred.</h1>
|
<h1>An error occurred.</h1>
|
||||||
<pre>
|
<pre>
|
||||||
|
|
|
@ -74,7 +74,7 @@ export const Sidebar = (props) => {
|
||||||
Советы и предложения
|
Советы и предложения
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<Off onClick={() => editorView().focus()} data-tauri-drag-region="true">
|
<Off onClick={() => editorView().focus()}>
|
||||||
<div class="sidebar-closer" onClick={toggleSidebar} />
|
<div class="sidebar-closer" onClick={toggleSidebar} />
|
||||||
<Show when={true}>
|
<Show when={true}>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { keymap } from 'prosemirror-keymap'
|
import { keymap } from 'prosemirror-keymap'
|
||||||
import { ProseMirrorExtension } from './helpers'
|
import type { ProseMirrorExtension } from './helpers'
|
||||||
import { Schema } from 'prosemirror-model'
|
import { Schema } from 'prosemirror-model'
|
||||||
import base from './extension/base'
|
import base from './extension/base'
|
||||||
import markdown from './extension/markdown'
|
import markdown from './extension/markdown'
|
||||||
|
@ -15,12 +15,13 @@ import dragHandle from './extension/drag-handle'
|
||||||
import pasteMarkdown from './extension/paste-markdown'
|
import pasteMarkdown from './extension/paste-markdown'
|
||||||
import table from './extension/table'
|
import table from './extension/table'
|
||||||
import collab from './extension/collab'
|
import collab from './extension/collab'
|
||||||
import { Config, YOptions } from '../store'
|
import type { Config, YOptions } from '../store'
|
||||||
import selectionMenu from './extension/selection'
|
import selectionMenu from './extension/selection'
|
||||||
|
import type { Command } from 'prosemirror-state'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data?: unknown
|
data?: unknown
|
||||||
keymap?: any
|
keymap?: { [key: string]: Command; }
|
||||||
config: Config
|
config: Config
|
||||||
markdown: boolean
|
markdown: boolean
|
||||||
path?: string
|
path?: string
|
||||||
|
|
|
@ -5,7 +5,7 @@ import type { WebrtcProvider } from 'y-webrtc'
|
||||||
import type { ProseMirrorExtension, ProseMirrorState } from '../prosemirror/helpers'
|
import type { ProseMirrorExtension, ProseMirrorState } from '../prosemirror/helpers'
|
||||||
import type { EditorView } from 'prosemirror-view'
|
import type { EditorView } from 'prosemirror-view'
|
||||||
import { createEmptyText } from '../prosemirror/setup'
|
import { createEmptyText } from '../prosemirror/setup'
|
||||||
import type { Shout } from '../../../graphql/types.gen'
|
import type { EditorState } from 'prosemirror-state'
|
||||||
|
|
||||||
export interface Args {
|
export interface Args {
|
||||||
draft: string // path to draft
|
draft: string // path to draft
|
||||||
|
@ -72,7 +72,7 @@ export interface Draft {
|
||||||
extensions?: ProseMirrorExtension[]
|
extensions?: ProseMirrorExtension[]
|
||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
body?: string
|
body?: string
|
||||||
text?: { doc: any; selection: { type: string; anchor: number; head: number } }
|
text?: { doc: EditorState['doc']; selection: { type: string; anchor: number; head: number } }
|
||||||
path?: string
|
path?: string
|
||||||
markdown?: boolean
|
markdown?: boolean
|
||||||
}
|
}
|
||||||
|
@ -122,6 +122,6 @@ export const addToDrafts = (drafts: Draft[], state: State): Draft[] => {
|
||||||
|
|
||||||
export const createTextFromDraft = async (draft: Draft) => {
|
export const createTextFromDraft = async (draft: Draft) => {
|
||||||
const created = createEmptyText()
|
const created = createEmptyText()
|
||||||
created.doc.content = Object.values(draft.text) // FIXME
|
created.doc.content = Object.values(draft.text) as any
|
||||||
return created
|
return created
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.beside-column {
|
.besideColumn {
|
||||||
counter-reset: item;
|
counter-reset: item;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
|
@ -29,17 +29,6 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 2em;
|
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;
|
align-items: baseline;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -87,14 +76,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.beside-column__topic {
|
.besideColumnTopic {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
margin-bottom: 0.4rem;
|
margin-bottom: 0.4rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.beside-column__shout {
|
.besideColumnShout {
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
|
|
||||||
h4 {
|
h4 {
|
|
@ -4,7 +4,7 @@ import { For, Show } from 'solid-js/web'
|
||||||
import { ArticleCard } from './Card'
|
import { ArticleCard } from './Card'
|
||||||
import { AuthorCard } from '../Author/Card'
|
import { AuthorCard } from '../Author/Card'
|
||||||
import { TopicCard } from '../Topic/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 type { Author, Shout, Topic, User } from '../../graphql/types.gen'
|
||||||
import { Icon } from '../Nav/Icon'
|
import { Icon } from '../Nav/Icon'
|
||||||
import { t } from '../../utils/intl'
|
import { t } from '../../utils/intl'
|
||||||
|
@ -15,8 +15,10 @@ interface BesideProps {
|
||||||
beside: Shout
|
beside: Shout
|
||||||
wrapper: 'topic' | 'author' | 'article' | 'top-article'
|
wrapper: 'topic' | 'author' | 'article' | 'top-article'
|
||||||
isTopicCompact?: boolean
|
isTopicCompact?: boolean
|
||||||
|
isTopicInRow?: boolean
|
||||||
topicShortDescription?: boolean
|
topicShortDescription?: boolean
|
||||||
topicsBySlug?: { [slug: string]: Topic }
|
topicsBySlug?: { [slug: string]: Topic }
|
||||||
|
iconButton?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (props: BesideProps) => {
|
export default (props: BesideProps) => {
|
||||||
|
@ -27,7 +29,7 @@ export default (props: BesideProps) => {
|
||||||
<Show when={!!props.values}>
|
<Show when={!!props.values}>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<Show when={!!props.title}>
|
<Show when={!!props.title}>
|
||||||
<div class="beside-column-title">
|
<div class={style.besideColumnTitle}>
|
||||||
<h4>{props.title}</h4>
|
<h4>{props.title}</h4>
|
||||||
|
|
||||||
<Show when={props.wrapper === 'author'}>
|
<Show when={props.wrapper === 'author'}>
|
||||||
|
@ -38,15 +40,17 @@ export default (props: BesideProps) => {
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<ul class="beside-column">
|
<ul class={style.besideColumn}>
|
||||||
<For each={[...props.values]}>
|
<For each={[...props.values]}>
|
||||||
{(value: Shout | User | Topic | Author) => (
|
{(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'}>
|
<Show when={props.wrapper === 'topic'}>
|
||||||
<TopicCard
|
<TopicCard
|
||||||
topic={value as Topic}
|
topic={value as Topic}
|
||||||
compact={props.isTopicCompact}
|
compact={props.isTopicCompact}
|
||||||
shortDescription={props.topicShortDescription}
|
shortDescription={props.topicShortDescription}
|
||||||
|
isTopicInRow={props.isTopicInRow}
|
||||||
|
iconButton={props.iconButton}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={props.wrapper === 'author'}>
|
<Show when={props.wrapper === 'author'}>
|
||||||
|
@ -58,7 +62,7 @@ export default (props: BesideProps) => {
|
||||||
<Show when={props.wrapper === 'top-article' && value?.slug}>
|
<Show when={props.wrapper === 'top-article' && value?.slug}>
|
||||||
<ArticleCard
|
<ArticleCard
|
||||||
article={value as Shout}
|
article={value as Shout}
|
||||||
settings={{ noimage: true, noauthor: true, nodate: true }}
|
settings={{ noimage: true, noauthor: true, nodate: true, isShort: true }}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
</li>
|
</li>
|
||||||
|
@ -68,7 +72,7 @@ export default (props: BesideProps) => {
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<ArticleCard article={props.beside} settings={{}} />
|
<ArticleCard article={props.beside} settings={{ isBigTitle: true }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,22 +1,12 @@
|
||||||
.shout-card {
|
.shoutCard {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
margin-bottom: 2.4rem;
|
margin-bottom: 2.4rem;
|
||||||
position: relative;
|
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 {
|
&:hover {
|
||||||
.shout-card__cover img {
|
.shoutCardCover img {
|
||||||
transform: scale(1.05);
|
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 {
|
a {
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -39,7 +35,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.shout-card__title .shout-card__link-container {
|
.shoutCardTitle .shoutCardLinkContainer {
|
||||||
background-color: #000;
|
background-color: #000;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
@ -47,11 +43,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__cover-container {
|
.shoutCardCoverContainer {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__cover {
|
.shoutCardCover {
|
||||||
height: 0;
|
height: 0;
|
||||||
margin-bottom: 1.6rem;
|
margin-bottom: 1.6rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -73,25 +69,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout__topic,
|
.shoutAuthor,
|
||||||
.shout__author {
|
.shoutDate {
|
||||||
a {
|
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shout__author,
|
|
||||||
.shout__date {
|
|
||||||
@include font-size(1.2rem);
|
@include font-size(1.2rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout__author {
|
.shoutAuthor {
|
||||||
margin-right: 1.6rem;
|
margin-right: 1.6rem;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
border: none;
|
border: none;
|
||||||
color: rgb(0 0 0 / 70%);
|
color: rgb(0 0 0 / 70%);
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
@ -99,15 +89,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout__date {
|
.shoutDate {
|
||||||
color: rgb(0 0 0 / 50%);
|
color: rgb(0 0 0 / 50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout__details {
|
.shoutDetails {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__title {
|
.shoutCardTitle {
|
||||||
@include font-size(2.2rem);
|
@include font-size(2.2rem);
|
||||||
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
@ -115,29 +105,33 @@
|
||||||
margin-bottom: 0.8rem;
|
margin-bottom: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__title,
|
.shoutCardTitle,
|
||||||
.shout-card__subtitle {
|
.shoutCardSubtitle {
|
||||||
&,
|
&,
|
||||||
.shout-card__link-container {
|
.shoutCardLinkContainer {
|
||||||
box-decoration-break: clone;
|
box-decoration-break: clone;
|
||||||
|
|
||||||
|
/* stylelint-disable-next-line */
|
||||||
|
-webkit-box-decoration-break: clone;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__subtitle {
|
.shoutCardSubtitle {
|
||||||
color: #696969;
|
color: #696969;
|
||||||
@include font-size(1.7rem);
|
@include font-size(1.7rem);
|
||||||
|
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
margin-bottom: 0.8rem;
|
margin-bottom: 0.8rem;
|
||||||
|
transition: color 0.2s, background-color 0.2s, box-shadow 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__link-container {
|
.shoutCardLinkContainer {
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: color 0.2s, background-color 0.2s, box-shadow 0.2s;
|
transition: color 0.2s, background-color 0.2s, box-shadow 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__edit-control {
|
.shoutCardEditControl {
|
||||||
border-radius: 2em;
|
border-radius: 2em;
|
||||||
min-height: 2.6em;
|
min-height: 2.6em;
|
||||||
padding: 0 1.4em;
|
padding: 0 1.4em;
|
||||||
|
@ -149,17 +143,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card.withcover {
|
.shoutCard.withcover {
|
||||||
padding: 2.4rem;
|
padding: 2.4rem;
|
||||||
|
|
||||||
&,
|
&,
|
||||||
a,
|
a,
|
||||||
.shout-card__title,
|
.shoutCardTitle,
|
||||||
.shout-card__subtitle {
|
.shoutCardSubtitle {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__cover {
|
.shoutCardCover {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -178,7 +172,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__type {
|
.shoutCardType {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
height: 3.2rem;
|
height: 3.2rem;
|
||||||
|
@ -213,7 +207,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__content {
|
.shoutCardContent {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
|
||||||
@include media-breakpoint-up(md) {
|
@include media-breakpoint-up(md) {
|
||||||
|
@ -229,7 +223,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.col-md-6 .col-md-6 {
|
.col-md-6 .col-md-6 {
|
||||||
.shout-card {
|
.shoutCard {
|
||||||
border-bottom: 1px solid rgb(255 255 255 / 20%);
|
border-bottom: 1px solid rgb(255 255 255 / 20%);
|
||||||
margin: 3.6rem 0 0;
|
margin: 3.6rem 0 0;
|
||||||
padding-bottom: 2rem;
|
padding-bottom: 2rem;
|
||||||
|
@ -244,71 +238,56 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__cover-container {
|
.shoutCardCoverContainer {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__title,
|
.shoutCardTitle,
|
||||||
.shout-card__subtitle {
|
.shoutCardSubtitle {
|
||||||
display: inline;
|
display: inline;
|
||||||
@include font-size(2.6rem);
|
@include font-size(2.6rem);
|
||||||
|
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__subtitle {
|
.shoutCardSubtitle {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout__author {
|
.shoutAuthor {
|
||||||
margin-top: 0.6em;
|
margin-top: 0.6em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.floor--important {
|
.shoutCardFloorImportant {
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shout-card {
|
|
||||||
margin-bottom: $grid-gutter-width;
|
margin-bottom: $grid-gutter-width;
|
||||||
}
|
|
||||||
|
|
||||||
.shout__author {
|
.shoutAuthor {
|
||||||
margin-top: 0.8rem;
|
margin-top: 0.8rem;
|
||||||
}
|
|
||||||
|
|
||||||
.col-md-6 {
|
a {
|
||||||
.shout-card__title,
|
color: rgb(255 255 255 / 50%);
|
||||||
.shout-card__subtitle {
|
|
||||||
display: inline;
|
|
||||||
@include font-size(2.6rem);
|
|
||||||
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shout-card__title {
|
|
||||||
margin-right: 0.3em;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__titles-container {
|
.shoutCardTitle {
|
||||||
|
a {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
background: #fff;
|
||||||
|
color: #000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shoutCardTitlesContainer {
|
||||||
a {
|
a {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.shout-card__title .shout-card__link-container {
|
.shoutCardTitle .shoutCardLinkContainer {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
@ -317,7 +296,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card--with-cover {
|
.shoutCardWithCover {
|
||||||
padding: 56.2% 2.4rem 0;
|
padding: 56.2% 2.4rem 0;
|
||||||
|
|
||||||
@include media-breakpoint-down(sm) {
|
@include media-breakpoint-down(sm) {
|
||||||
|
@ -325,7 +304,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.swiper-slide {
|
&.swiper-slide {
|
||||||
.shout-card__content {
|
.shoutCardContent {
|
||||||
@include media-breakpoint-down(md) {
|
@include media-breakpoint-down(md) {
|
||||||
padding-left: 10%;
|
padding-left: 10%;
|
||||||
}
|
}
|
||||||
|
@ -334,25 +313,25 @@
|
||||||
|
|
||||||
&,
|
&,
|
||||||
a,
|
a,
|
||||||
.shout-card__title,
|
.shoutCardTitle,
|
||||||
.shout-card__subtitle {
|
.shoutCardSubtitle {
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout__author,
|
.shoutAuthor,
|
||||||
.shout-card__titles-container {
|
.shoutCardTitlesContainer {
|
||||||
a:hover {
|
a:hover {
|
||||||
&,
|
&,
|
||||||
.shout-card__title .shout-card__link-container {
|
.shoutCardTitle .shoutCardLinkContainer {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
color: #000 !important;
|
color: #000 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__cover-container,
|
.shoutCardCoverContainer,
|
||||||
.shout-card__cover,
|
.shoutCardCover,
|
||||||
.shout-card__content {
|
.shoutCardContent {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -362,7 +341,7 @@
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__content {
|
.shoutCardContent {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: end;
|
justify-content: end;
|
||||||
|
@ -370,7 +349,7 @@
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__cover {
|
.shoutCardCover {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
|
@ -383,43 +362,37 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__title {
|
.shoutCardTitle {
|
||||||
@include font-size(3.2rem);
|
@include font-size(3.2rem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card--content-top {
|
.shoutCardContentTop {
|
||||||
.shout-card__content {
|
.shoutCardContent {
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card--short {
|
.shoutCardPhotoBottom {
|
||||||
.shout-card__title {
|
.shoutCardContent {
|
||||||
@include font-size(1.7rem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shout-card--photo-bottom {
|
|
||||||
.shout-card__content {
|
|
||||||
margin-bottom: 1.6rem;
|
margin-bottom: 1.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__cover-container {
|
.shoutCardCoverContainer {
|
||||||
order: 2;
|
order: 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card--feed {
|
.shoutCardFeed {
|
||||||
border-bottom: 1px solid #e8e8e8;
|
border-bottom: 1px solid #e8e8e8;
|
||||||
margin-bottom: 2em;
|
margin-bottom: 2em;
|
||||||
|
|
||||||
.shout-card__content {
|
.shoutCardContent {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__details {
|
.shoutCardDetails {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -431,11 +404,11 @@
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__details-content {
|
.shoutCardDetailsContent {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__details-item {
|
.shoutCardDetailsTtem {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-right: 1.7em;
|
margin-right: 1.7em;
|
||||||
|
@ -454,7 +427,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__comments {
|
.shoutCardComments {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
@ -476,12 +449,12 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rating__value {
|
.ratingValue {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin: 0 0.5em;
|
margin: 0 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rating__control {
|
.ratingControl {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 2px solid;
|
border: 2px solid;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
|
@ -501,20 +474,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.floor--7 {
|
.shoutCardVertical {
|
||||||
@include media-breakpoint-down(md) {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.col-md-6 {
|
|
||||||
margin-bottom: 1.6em;
|
|
||||||
|
|
||||||
@include media-breakpoint-down(md) {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shout-card {
|
|
||||||
aspect-ratio: auto;
|
aspect-ratio: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 1.6rem 0;
|
margin: 1.6rem 0;
|
||||||
|
@ -524,9 +484,8 @@
|
||||||
aspect-ratio: 1 / 1.6;
|
aspect-ratio: 1 / 1.6;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.shout-card__title {
|
.shoutCardTitle {
|
||||||
font-size: 2.6rem !important;
|
font-size: 2.6rem !important;
|
||||||
|
|
||||||
@include media-breakpoint-between(lg, xl) {
|
@include media-breakpoint-between(lg, xl) {
|
||||||
|
@ -534,11 +493,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__content {
|
.shoutCardContent {
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.shout-card__subtitle {
|
.shoutCardSubtitle {
|
||||||
@include media-breakpoint-up(lg) {
|
@include media-breakpoint-up(lg) {
|
||||||
@include font-size(2rem);
|
@include font-size(2rem);
|
||||||
}
|
}
|
||||||
|
@ -552,44 +511,151 @@
|
||||||
|
|
||||||
.floor--9 {
|
.floor--9 {
|
||||||
/* TODO: refactor these styles */
|
/* TODO: refactor these styles */
|
||||||
.shout-card__title,
|
.shoutCardTitle,
|
||||||
.shout-card__subtitle {
|
.shoutCardSubtitle {
|
||||||
// display: inline;
|
// display: inline;
|
||||||
font-size: 2.2rem;
|
font-size: 2.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__title {
|
.shoutCardTitle {
|
||||||
padding-right: 0.25em;
|
padding-right: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout__author,
|
.shoutAuthor,
|
||||||
.shout__date {
|
.shoutDate {
|
||||||
margin-top: 0.8rem;
|
margin-top: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.beside-column .shout-card__titles-container {
|
.beside-column .shoutCardTitlesContainer {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
.shout-card__link-container {
|
.shoutCardLinkContainer {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.col-md-6,
|
.shoutCardBigTitle {
|
||||||
.col-lg-6,
|
.shoutCardTitle {
|
||||||
.col-md-8 {
|
|
||||||
.shout-card__title {
|
|
||||||
display: block;
|
display: block;
|
||||||
@include font-size(3.2rem);
|
@include font-size(3.2rem);
|
||||||
|
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout-card__subtitle {
|
.shoutCardSubtitle {
|
||||||
color: #696969;
|
color: #696969;
|
||||||
@include font-size(2.4rem);
|
@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 { capitalize } from '../../utils'
|
||||||
import { translit } from '../../utils/ru2en'
|
import { translit } from '../../utils/ru2en'
|
||||||
import { Icon } from '../Nav/Icon'
|
import { Icon } from '../Nav/Icon'
|
||||||
import './Card.scss'
|
import style from './Card.module.scss'
|
||||||
import { locale } from '../../stores/ui'
|
import { locale } from '../../stores/ui'
|
||||||
import { handleClientRouteLinkClick } from '../../stores/router'
|
import { handleClientRouteLinkClick } from '../../stores/router'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
import CardTopic from './CardTopic'
|
||||||
|
|
||||||
interface ArticleCardProps {
|
interface ArticleCardProps {
|
||||||
settings?: {
|
settings?: {
|
||||||
|
@ -20,6 +22,14 @@ interface ArticleCardProps {
|
||||||
photoBottom?: boolean
|
photoBottom?: boolean
|
||||||
additionalClass?: string
|
additionalClass?: string
|
||||||
isFeedMode?: boolean
|
isFeedMode?: boolean
|
||||||
|
isFloorImportant?: boolean
|
||||||
|
isWithCover?: boolean
|
||||||
|
isBigTitle?: boolean
|
||||||
|
isVertical?: boolean
|
||||||
|
isShort?: boolean
|
||||||
|
withBorder?: boolean
|
||||||
|
isCompact?: boolean
|
||||||
|
isSingle?: boolean
|
||||||
}
|
}
|
||||||
article: Shout
|
article: Shout
|
||||||
}
|
}
|
||||||
|
@ -62,56 +72,65 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
class={`shout-card ${props.settings?.additionalClass || ''}`}
|
class={clsx(style.shoutCard, `${props.settings?.additionalClass || ''}`)}
|
||||||
classList={{
|
classList={{
|
||||||
'shout-card--short': props.settings?.noimage,
|
[style.shoutCardShort]: props.settings?.isShort,
|
||||||
'shout-card--photo-bottom': props.settings?.noimage && props.settings?.photoBottom,
|
[style.shoutCardPhotoBottom]: props.settings?.noimage && props.settings?.photoBottom,
|
||||||
'shout-card--feed': props.settings?.isFeedMode
|
[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}>
|
<Show when={!props.settings?.noimage && cover}>
|
||||||
<div class="shout-card__cover-container">
|
<div class={style.shoutCardCoverContainer}>
|
||||||
<div class="shout-card__cover">
|
<div class={style.shoutCardCover}>
|
||||||
<img src={cover || ''} alt={title || ''} loading="lazy" />
|
<img src={cover || ''} alt={title || ''} loading="lazy" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<div class="shout-card__content">
|
<div class={style.shoutCardContent}>
|
||||||
<Show when={layout && layout !== 'article' && !(props.settings?.noicon || props.settings?.noimage)}>
|
<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}`}>
|
<a href={`/topic/${mainTopic.slug}`}>
|
||||||
<Icon name={layout} />
|
<Icon name={layout} class={style.icon} />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={!props.settings?.isGroup}>
|
<Show when={!props.settings?.isGroup}>
|
||||||
<div class="shout__topic">
|
<CardTopic
|
||||||
<a href={`/topic/${mainTopic.slug}`}>
|
title={
|
||||||
{locale() === 'ru' && mainTopic.title ? mainTopic.title : mainTopic.slug.replace('-', ' ')}
|
locale() === 'ru' && mainTopic.title ? mainTopic.title : mainTopic.slug.replace('-', ' ')
|
||||||
</a>
|
}
|
||||||
</div>
|
slug={mainTopic.slug}
|
||||||
|
isFloorImportant={props.settings?.isFloorImportant}
|
||||||
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<div class="shout-card__titles-container">
|
<div class={style.shoutCardTitlesContainer}>
|
||||||
<a href={`/${slug || ''}`} onClick={handleClientRouteLinkClick}>
|
<a href={`/${slug || ''}`} onClick={handleClientRouteLinkClick}>
|
||||||
<div class="shout-card__title">
|
<div class={style.shoutCardTitle}>
|
||||||
<span class="shout-card__link-container">{title}</span>
|
<span class={style.shoutCardLinkContainer}>{title}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={!props.settings?.nosubtitle && subtitle}>
|
<Show when={!props.settings?.nosubtitle && subtitle}>
|
||||||
<div class="shout-card__subtitle">
|
<div class={style.shoutCardSubtitle}>
|
||||||
<span class="shout-card__link-container">{subtitle}</span>
|
<span class={style.shoutCardLinkContainer}>{subtitle}</span>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={!props.settings?.noauthor || !props.settings?.nodate}>
|
<Show when={!props.settings?.noauthor || !props.settings?.nodate}>
|
||||||
<div class="shout__details">
|
<div class={style.shoutDetails}>
|
||||||
<Show when={!props.settings?.noauthor}>
|
<Show when={!props.settings?.noauthor}>
|
||||||
<div class="shout__author">
|
<div class={style.shoutAuthor}>
|
||||||
<For each={authors}>
|
<For each={authors}>
|
||||||
{(author, index) => {
|
{(author, index) => {
|
||||||
const name =
|
const name =
|
||||||
|
@ -131,39 +150,39 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={!props.settings?.nodate}>
|
<Show when={!props.settings?.nodate}>
|
||||||
<div class="shout__date">{formattedDate()}</div>
|
<div class={style.shoutDate}>{formattedDate()}</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={props.settings?.isFeedMode}>
|
<Show when={props.settings?.isFeedMode}>
|
||||||
<section class="shout-card__details">
|
<section class={style.shoutCardDetails}>
|
||||||
<div class="shout-card__details-content">
|
<div class={style.shoutCardDetailsContent}>
|
||||||
<div class="shout-card__details-item rating">
|
<div class={clsx(style.shoutCardDetailsItem, 'rating')}>
|
||||||
<button class="rating__control">−</button>
|
<button class="rating__control">−</button>
|
||||||
<span class="rating__value">{stat?.rating || ''}</span>
|
<span class="rating__value">{stat?.rating || ''}</span>
|
||||||
<button class="rating__control">+</button>
|
<button class="rating__control">+</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="shout-card__details-item shout-card__comments">
|
<div class={clsx(style.shoutCardDetailsItem, style.shoutCardComments)}>
|
||||||
<Icon name="eye" />
|
<Icon name="eye" class={style.icon} />
|
||||||
{stat?.viewed}
|
{stat?.viewed}
|
||||||
</div>
|
</div>
|
||||||
<div class="shout-card__details-item shout-card__comments">
|
<div class={clsx(style.shoutCardDetailsTtem, style.shoutCardComments)}>
|
||||||
<a href={`/${slug + '#comments' || ''}`}>
|
<a href={`/${slug + '#comments' || ''}`}>
|
||||||
<Icon name="comment" />
|
<Icon name="comment" class={style.icon} />
|
||||||
{stat?.commented || ''}
|
{stat?.commented || ''}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="shout-card__details-item">
|
<div class={style.shoutCardDetailsItem}>
|
||||||
<button>
|
<button>
|
||||||
<Icon name="bookmark" />
|
<Icon name="bookmark" class={style.icon} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="shout-card__details-item">
|
<div class={style.shoutCardDetailsItem}>
|
||||||
<button>
|
<button>
|
||||||
<Icon name="ellipsis" />
|
<Icon name="ellipsis" class={style.icon} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</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;
|
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) => {
|
export default (props: GroupProps) => {
|
||||||
if (!props.articles) props.articles = []
|
if (!props.articles) props.articles = []
|
||||||
return (
|
return (
|
||||||
<div class="floor floor--important">
|
<div class="floor floor--important floor--group">
|
||||||
<Show when={props.articles.length > 4}>
|
<Show when={props.articles.length > 4}>
|
||||||
<div class="wide-container row">
|
<div class="wide-container row">
|
||||||
<div class="group__header col-12">{props.header}</div>
|
<div class="group__header col-12">{props.header}</div>
|
||||||
|
|
||||||
<div class="col-lg-6">
|
<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>
|
||||||
|
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
|
@ -28,7 +31,10 @@ export default (props: GroupProps) => {
|
||||||
{(a) => (
|
{(a) => (
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -37,12 +43,34 @@ export default (props: GroupProps) => {
|
||||||
<Show when={props.articles.length >= 4}>
|
<Show when={props.articles.length >= 4}>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<For each={props.articles.slice(1, 3)}>
|
<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>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<For each={props.articles.slice(3, 5)}>
|
<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>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</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 { Show } from 'solid-js'
|
||||||
import type { Shout } from '../../graphql/types.gen'
|
import type { Shout } from '../../graphql/types.gen'
|
||||||
import { ArticleCard } from './Card'
|
import { ArticleCard } from './Card'
|
||||||
import './Row1.scss'
|
|
||||||
|
|
||||||
export default (props: { article: Shout }) => (
|
export default (props: { article: Shout }) => (
|
||||||
<Show when={!!props.article}>
|
<Show when={!!props.article}>
|
||||||
<div class="floor floor--one-article">
|
<div class="floor floor--one-article">
|
||||||
<div class="wide-container row">
|
<div class="wide-container row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<ArticleCard article={props.article} />
|
<ArticleCard article={props.article} settings={{isSingle: true}} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -21,12 +21,7 @@ export default (props: { articles: Shout[] }) => {
|
||||||
return (
|
return (
|
||||||
<Show when={!!a}>
|
<Show when={!!a}>
|
||||||
<div class={`col-md-${x[y()][i()]}`}>
|
<div class={`col-md-${x[y()][i()]}`}>
|
||||||
<ArticleCard
|
<ArticleCard article={a} settings={{ isWithCover: x[y()][i()] === '8' }} />
|
||||||
article={a}
|
|
||||||
settings={{
|
|
||||||
additionalClass: x[y()][i()] === '8' ? 'shout-card--with-cover' : ''
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,14 +7,14 @@ export const Row5 = (props: { articles: Shout[] }) => {
|
||||||
<div class="wide-container row">
|
<div class="wide-container row">
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<ArticleCard article={props.articles[0]} />
|
<ArticleCard article={props.articles[0]} />
|
||||||
<ArticleCard article={props.articles[1]} />
|
<ArticleCard article={props.articles[1]} settings={{ noimage: true, withBorder: true }} />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<ArticleCard article={props.articles[2]} />
|
<ArticleCard article={props.articles[2]} settings={{ isBigTitle: true }} />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<ArticleCard article={props.articles[3]} />
|
<ArticleCard article={props.articles[3]} />
|
||||||
<ArticleCard article={props.articles[4]} />
|
<ArticleCard article={props.articles[4]} settings={{ noimage: true, withBorder: true }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,7 +10,12 @@ export default (props: { articles: Shout[] }) => (
|
||||||
<div class="col-md-6 col-lg-3">
|
<div class="col-md-6 col-lg-3">
|
||||||
<ArticleCard
|
<ArticleCard
|
||||||
article={a}
|
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>
|
</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 {
|
.swiper-slide {
|
||||||
height: 0 !important;
|
height: 0 !important;
|
||||||
min-height: 0 !important;
|
min-height: 0 !important;
|
||||||
|
|
|
@ -60,7 +60,12 @@ export default (props: SliderProps) => {
|
||||||
{(a: Shout) => (
|
{(a: Shout) => (
|
||||||
<ArticleCard
|
<ArticleCard
|
||||||
article={a}
|
article={a}
|
||||||
settings={{ additionalClass: 'shout-card--with-cover swiper-slide' }}
|
settings={{
|
||||||
|
additionalClass: 'swiper-slide',
|
||||||
|
isFloorImportant: true,
|
||||||
|
isWithCover: true,
|
||||||
|
nodate: true
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</For>
|
</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 { useValidator } from '../../utils/validators'
|
||||||
import { baseUrl } from '../../graphql/publicGraphQLClient'
|
import { baseUrl } from '../../graphql/publicGraphQLClient'
|
||||||
import { ApiError } from '../../utils/apiClient'
|
import { ApiError } from '../../utils/apiClient'
|
||||||
|
import { handleClientRouteLinkClick } from '../../stores/router'
|
||||||
|
|
||||||
type AuthMode = 'sign-in' | 'sign-up' | 'forget' | 'reset' | 'resend' | 'password'
|
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('.')
|
// 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 !!!
|
// FIXME !!!
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// 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 { session } = useAuthStore()
|
||||||
const [handshaking] = createSignal(false)
|
const [handshaking] = createSignal(false)
|
||||||
const { getModal } = useModalStore()
|
const { getModal } = useModalStore()
|
||||||
|
@ -45,13 +53,6 @@ export default (props: { code?: string; mode?: string }) => {
|
||||||
let passElement: HTMLInputElement | undefined
|
let passElement: HTMLInputElement | undefined
|
||||||
let codeElement: 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
|
// FIXME: restore logic
|
||||||
// const usedEmails = {}
|
// const usedEmails = {}
|
||||||
// const checkEmailAsync = async (email: string) => {
|
// const checkEmailAsync = async (email: string) => {
|
||||||
|
@ -185,9 +186,15 @@ export default (props: { code?: string; mode?: string }) => {
|
||||||
{t('New stories every day and even more!')}
|
{t('New stories every day and even more!')}
|
||||||
</p>
|
</p>
|
||||||
<p class="disclamer">
|
<p class="disclamer">
|
||||||
{t('By signing up you agree with our')}
|
{t('By signing up you agree with our')}{' '}
|
||||||
<a href="/about/terms-of-use" onClick={hideModal}>
|
<a
|
||||||
{' ' + t('terms of use')}
|
href="/about/terms-of-use"
|
||||||
|
onClick={(event) => {
|
||||||
|
hideModal()
|
||||||
|
handleClientRouteLinkClick(event)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('terms of use')}
|
||||||
</a>
|
</a>
|
||||||
, {t('personal data usage and email notifications')}.
|
, {t('personal data usage and email notifications')}.
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -20,6 +20,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mainHeaderInner {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.headerFixed.headerScrolledBottom,
|
.headerFixed.headerScrolledBottom,
|
||||||
.headerFixed.headerScrolledTop {
|
.headerFixed.headerScrolledTop {
|
||||||
.mainLogo {
|
.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 {
|
.headerFixed {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
||||||
|
.fixed & {
|
||||||
|
bottom: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.headerInner {
|
.headerInner {
|
||||||
|
@ -44,8 +65,7 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
&.fixed {
|
.fixed & {
|
||||||
border-bottom: 4px solid #000;
|
|
||||||
left: 0;
|
left: 0;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
@ -62,25 +82,33 @@
|
||||||
.mainLogo {
|
.mainLogo {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
height: 70px;
|
height: 56px;
|
||||||
padding: 0 $container-padding-x 0 0;
|
padding: 0 $container-padding-x 0 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: height 0.2s;
|
transition: height 0.2s;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
z-index: 9;
|
z-index: 9;
|
||||||
|
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
height: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
@include media-breakpoint-up(lg) {
|
@include media-breakpoint-up(lg) {
|
||||||
height: 80px;
|
height: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
height: 32px;
|
height: 20px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
object-position: left;
|
object-position: left;
|
||||||
transition: height 0.2s;
|
transition: height 0.2s;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
|
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
@include media-breakpoint-up(lg) {
|
@include media-breakpoint-up(lg) {
|
||||||
width: 175px;
|
width: 175px;
|
||||||
}
|
}
|
||||||
|
@ -113,6 +141,14 @@
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
@include font-size(1.7rem);
|
@include font-size(1.7rem);
|
||||||
|
|
||||||
|
@include media-breakpoint-down(md) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed & {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mainNavigation {
|
.mainNavigation {
|
||||||
|
@ -145,7 +181,7 @@
|
||||||
padding: divide($container-padding-x, 2);
|
padding: divide($container-padding-x, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.fixed {
|
.fixed & {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|
||||||
@include media-breakpoint-down(lg) {
|
@include media-breakpoint-down(lg) {
|
||||||
|
@ -181,12 +217,8 @@
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
float: right;
|
float: right;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
padding-left: 0;
|
|
||||||
width: 2.2rem;
|
|
||||||
|
|
||||||
@include media-breakpoint-up(sm) {
|
|
||||||
padding-left: divide($container-padding-x, 2);
|
padding-left: divide($container-padding-x, 2);
|
||||||
}
|
width: 2.2rem;
|
||||||
|
|
||||||
@include media-breakpoint-up(md) {
|
@include media-breakpoint-up(md) {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -235,7 +267,7 @@
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.fixed {
|
.fixed & {
|
||||||
> div {
|
> div {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0s;
|
transition: opacity 0s;
|
||||||
|
@ -276,6 +308,10 @@
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
@include media-breakpoint-down(md) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.headerSearch {
|
.headerSearch {
|
||||||
|
@ -305,6 +341,7 @@
|
||||||
|
|
||||||
.articleControls {
|
.articleControls {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
left: 0;
|
left: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
|
|
|
@ -3,12 +3,14 @@ import Private from './Private'
|
||||||
import Notifications from './Notifications'
|
import Notifications from './Notifications'
|
||||||
import { Icon } from './Icon'
|
import { Icon } from './Icon'
|
||||||
import { Modal } from './Modal'
|
import { Modal } from './Modal'
|
||||||
|
import { Popup } from './Popup'
|
||||||
import AuthModal from './AuthModal'
|
import AuthModal from './AuthModal'
|
||||||
import { t } from '../../utils/intl'
|
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 { useAuthStore } from '../../stores/auth'
|
||||||
import { handleClientRouteLinkClick, router, Routes, useRouter } from '../../stores/router'
|
import { handleClientRouteLinkClick, router, Routes, useRouter } from '../../stores/router'
|
||||||
import styles from './Header.module.scss'
|
import styles from './Header.module.scss'
|
||||||
|
import stylesPopup from './Popup.module.scss'
|
||||||
import privateStyles from './Private.module.scss'
|
import privateStyles from './Private.module.scss'
|
||||||
import { getPagePath } from '@nanostores/router'
|
import { getPagePath } from '@nanostores/router'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
@ -46,11 +48,10 @@ export const Header = (props: Props) => {
|
||||||
const toggleFixed = () => setFixed(!fixed())
|
const toggleFixed = () => setFixed(!fixed())
|
||||||
// effects
|
// effects
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (fixed() || getModal()) {
|
const isFixed = fixed() || (getModal() && getModal() !== 'share');
|
||||||
document.body.classList.add('fixed')
|
|
||||||
} else {
|
document.body.classList.toggle('fixed', isFixed);
|
||||||
document.body.classList.remove('fixed')
|
document.body.classList.toggle(styles.fixed, isFixed);
|
||||||
}
|
|
||||||
}, [fixed(), getModal()])
|
}, [fixed(), getModal()])
|
||||||
|
|
||||||
// derived
|
// derived
|
||||||
|
@ -68,8 +69,6 @@ export const Header = (props: Props) => {
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
let scrollTop = window.scrollY
|
let scrollTop = window.scrollY
|
||||||
|
|
||||||
// window.console.log(props.title)
|
|
||||||
|
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
setIsScrollingBottom(window.scrollY > scrollTop)
|
setIsScrollingBottom(window.scrollY > scrollTop)
|
||||||
setIsScrolled(window.scrollY > 0)
|
setIsScrolled(window.scrollY > 0)
|
||||||
|
@ -95,7 +94,43 @@ export const Header = (props: Props) => {
|
||||||
<Modal name="auth">
|
<Modal name="auth">
|
||||||
<AuthModal />
|
<AuthModal />
|
||||||
</Modal>
|
</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() }}>
|
<nav class={clsx(styles.headerInner, 'row')} classList={{ fixed: fixed() }}>
|
||||||
<div class={clsx(styles.mainLogo, 'col-auto')}>
|
<div class={clsx(styles.mainLogo, 'col-auto')}>
|
||||||
<a href={getPagePath(router, 'home')} onClick={handleClientRouteLinkClick}>
|
<a href={getPagePath(router, 'home')} onClick={handleClientRouteLinkClick}>
|
||||||
|
@ -113,7 +148,7 @@ export const Header = (props: Props) => {
|
||||||
>
|
>
|
||||||
<For each={resources}>
|
<For each={resources}>
|
||||||
{(r) => (
|
{(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}>
|
<a href={getPagePath(router, r.route, null)} onClick={handleClientRouteLinkClick}>
|
||||||
{r.name}
|
{r.name}
|
||||||
</a>
|
</a>
|
||||||
|
@ -159,7 +194,9 @@ export const Header = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
<Show when={props.title}>
|
<Show when={props.title}>
|
||||||
<div class={styles.articleControls}>
|
<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">
|
<a href="#comments">
|
||||||
<Icon name="comments-outline" class={styles.icon} />
|
<Icon name="comments-outline" class={styles.icon} />
|
||||||
</a>
|
</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" />
|
<Icon name="pencil" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class={clsx(styles.userControlItem, styles.userControlItemSearch)}>
|
|
||||||
<a href="/search">
|
|
||||||
<Icon name="search" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class={clsx(styles.userControlItem, styles.userControlItemInbox)}>
|
<div class={clsx(styles.userControlItem, styles.userControlItemInbox)}>
|
||||||
<a href="/inbox">
|
<a href="/inbox">
|
||||||
{/*FIXME: replace with route*/}
|
{/*FIXME: replace with route*/}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { AllAuthorsView } from '../Views/AllAuthors'
|
||||||
import type { PageProps } from '../types'
|
import type { PageProps } from '../types'
|
||||||
import { createSignal, onMount, Show } from 'solid-js'
|
import { createSignal, onMount, Show } from 'solid-js'
|
||||||
import { loadAllAuthors } from '../../stores/zine/authors'
|
import { loadAllAuthors } from '../../stores/zine/authors'
|
||||||
import { t } from '../../utils/intl'
|
import { Loading } from '../Loading'
|
||||||
|
|
||||||
export const AllAuthorsPage = (props: PageProps) => {
|
export const AllAuthorsPage = (props: PageProps) => {
|
||||||
const [isLoaded, setIsLoaded] = createSignal<boolean>(Boolean(props.allAuthors))
|
const [isLoaded, setIsLoaded] = createSignal<boolean>(Boolean(props.allAuthors))
|
||||||
|
@ -19,7 +19,7 @@ export const AllAuthorsPage = (props: PageProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainLayout>
|
<MainLayout>
|
||||||
<Show when={isLoaded()} fallback={t('Loading')}>
|
<Show when={isLoaded()} fallback={<Loading />}>
|
||||||
<AllAuthorsView authors={props.allAuthors} />
|
<AllAuthorsView authors={props.allAuthors} />
|
||||||
</Show>
|
</Show>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
|
|
|
@ -2,8 +2,8 @@ import { MainLayout } from '../Layouts/MainLayout'
|
||||||
import { AllTopicsView } from '../Views/AllTopics'
|
import { AllTopicsView } from '../Views/AllTopics'
|
||||||
import type { PageProps } from '../types'
|
import type { PageProps } from '../types'
|
||||||
import { createSignal, onMount, Show } from 'solid-js'
|
import { createSignal, onMount, Show } from 'solid-js'
|
||||||
import { t } from '../../utils/intl'
|
|
||||||
import { loadAllTopics } from '../../stores/zine/topics'
|
import { loadAllTopics } from '../../stores/zine/topics'
|
||||||
|
import { Loading } from '../Loading'
|
||||||
|
|
||||||
export const AllTopicsPage = (props: PageProps) => {
|
export const AllTopicsPage = (props: PageProps) => {
|
||||||
const [isLoaded, setIsLoaded] = createSignal<boolean>(Boolean(props.allTopics))
|
const [isLoaded, setIsLoaded] = createSignal<boolean>(Boolean(props.allTopics))
|
||||||
|
@ -19,7 +19,7 @@ export const AllTopicsPage = (props: PageProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainLayout>
|
<MainLayout>
|
||||||
<Show when={isLoaded()} fallback={t('Loading')}>
|
<Show when={isLoaded()} fallback={<Loading />}>
|
||||||
<AllTopicsView topics={props.allTopics} />
|
<AllTopicsView topics={props.allTopics} />
|
||||||
</Show>
|
</Show>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
|
|
|
@ -3,9 +3,9 @@ import { ArticleView } from '../Views/Article'
|
||||||
import type { PageProps } from '../types'
|
import type { PageProps } from '../types'
|
||||||
import { loadArticle, useArticlesStore } from '../../stores/zine/articles'
|
import { loadArticle, useArticlesStore } from '../../stores/zine/articles'
|
||||||
import { createMemo, onMount, Show } from 'solid-js'
|
import { createMemo, onMount, Show } from 'solid-js'
|
||||||
import { t } from '../../utils/intl'
|
|
||||||
import type { Shout } from '../../graphql/types.gen'
|
import type { Shout } from '../../graphql/types.gen'
|
||||||
import { useRouter } from '../../stores/router'
|
import { useRouter } from '../../stores/router'
|
||||||
|
import { Loading } from '../Loading'
|
||||||
|
|
||||||
export const ArticlePage = (props: PageProps) => {
|
export const ArticlePage = (props: PageProps) => {
|
||||||
const sortedArticles = props.article ? [props.article] : []
|
const sortedArticles = props.article ? [props.article] : []
|
||||||
|
@ -38,7 +38,7 @@ export const ArticlePage = (props: PageProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainLayout headerTitle={article()?.title || ''}>
|
<MainLayout headerTitle={article()?.title || ''}>
|
||||||
<Show when={Boolean(article())} fallback={t('Loading')}>
|
<Show when={Boolean(article())} fallback={<Loading />}>
|
||||||
<ArticleView article={article()} />
|
<ArticleView article={article()} />
|
||||||
</Show>
|
</Show>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
|
|
|
@ -4,8 +4,8 @@ import type { PageProps } from '../types'
|
||||||
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||||
import { loadArticlesForAuthors, resetSortedArticles } from '../../stores/zine/articles'
|
import { loadArticlesForAuthors, resetSortedArticles } from '../../stores/zine/articles'
|
||||||
import { useRouter } from '../../stores/router'
|
import { useRouter } from '../../stores/router'
|
||||||
import { t } from '../../utils/intl'
|
|
||||||
import { loadAuthor } from '../../stores/zine/authors'
|
import { loadAuthor } from '../../stores/zine/authors'
|
||||||
|
import { Loading } from '../Loading'
|
||||||
|
|
||||||
export const AuthorPage = (props: PageProps) => {
|
export const AuthorPage = (props: PageProps) => {
|
||||||
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.authorArticles) && Boolean(props.author))
|
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.authorArticles) && Boolean(props.author))
|
||||||
|
@ -37,7 +37,7 @@ export const AuthorPage = (props: PageProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainLayout>
|
<MainLayout>
|
||||||
<Show when={isLoaded()} fallback={t('Loading')}>
|
<Show when={isLoaded()} fallback={<Loading />}>
|
||||||
<AuthorView author={props.author} authorArticles={props.authorArticles} authorSlug={slug()} />
|
<AuthorView author={props.author} authorArticles={props.authorArticles} authorSlug={slug()} />
|
||||||
</Show>
|
</Show>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { FeedView } from '../Views/Feed'
|
||||||
import type { PageProps } from '../types'
|
import type { PageProps } from '../types'
|
||||||
import { createSignal, onCleanup, onMount, Show } from 'solid-js'
|
import { createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||||
import { loadRecentArticles, resetSortedArticles } from '../../stores/zine/articles'
|
import { loadRecentArticles, resetSortedArticles } from '../../stores/zine/articles'
|
||||||
import { t } from '../../utils/intl'
|
import { Loading } from '../Loading'
|
||||||
|
|
||||||
export const FeedPage = (props: PageProps) => {
|
export const FeedPage = (props: PageProps) => {
|
||||||
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.feedArticles))
|
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.feedArticles))
|
||||||
|
@ -22,7 +22,7 @@ export const FeedPage = (props: PageProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainLayout>
|
<MainLayout>
|
||||||
<Show when={isLoaded()} fallback={t('Loading')}>
|
<Show when={isLoaded()} fallback={<Loading />}>
|
||||||
<FeedView articles={props.feedArticles} />
|
<FeedView articles={props.feedArticles} />
|
||||||
</Show>
|
</Show>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
|
|
|
@ -2,9 +2,9 @@ import { HomeView } from '../Views/Home'
|
||||||
import { MainLayout } from '../Layouts/MainLayout'
|
import { MainLayout } from '../Layouts/MainLayout'
|
||||||
import type { PageProps } from '../types'
|
import type { PageProps } from '../types'
|
||||||
import { createSignal, onCleanup, onMount, Show } from 'solid-js'
|
import { createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||||
import { t } from '../../utils/intl'
|
|
||||||
import { loadPublishedArticles, resetSortedArticles } from '../../stores/zine/articles'
|
import { loadPublishedArticles, resetSortedArticles } from '../../stores/zine/articles'
|
||||||
import { loadRandomTopics } from '../../stores/zine/topics'
|
import { loadRandomTopics } from '../../stores/zine/topics'
|
||||||
|
import { Loading } from '../Loading'
|
||||||
|
|
||||||
export const HomePage = (props: PageProps) => {
|
export const HomePage = (props: PageProps) => {
|
||||||
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.homeArticles) && Boolean(props.randomTopics))
|
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.homeArticles) && Boolean(props.randomTopics))
|
||||||
|
@ -24,7 +24,7 @@ export const HomePage = (props: PageProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainLayout>
|
<MainLayout>
|
||||||
<Show when={isLoaded()} fallback={t('Loading')}>
|
<Show when={isLoaded()} fallback={<Loading />}>
|
||||||
<HomeView randomTopics={props.randomTopics} recentPublishedArticles={props.homeArticles || []} />
|
<HomeView randomTopics={props.randomTopics} recentPublishedArticles={props.homeArticles || []} />
|
||||||
</Show>
|
</Show>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
|
|
|
@ -3,8 +3,8 @@ import { SearchView } from '../Views/Search'
|
||||||
import type { PageProps } from '../types'
|
import type { PageProps } from '../types'
|
||||||
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||||
import { loadSearchResults, resetSortedArticles } from '../../stores/zine/articles'
|
import { loadSearchResults, resetSortedArticles } from '../../stores/zine/articles'
|
||||||
import { t } from '../../utils/intl'
|
|
||||||
import { useRouter } from '../../stores/router'
|
import { useRouter } from '../../stores/router'
|
||||||
|
import { Loading } from '../Loading'
|
||||||
|
|
||||||
export const SearchPage = (props: PageProps) => {
|
export const SearchPage = (props: PageProps) => {
|
||||||
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.searchResults))
|
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.searchResults))
|
||||||
|
@ -34,7 +34,7 @@ export const SearchPage = (props: PageProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainLayout>
|
<MainLayout>
|
||||||
<Show when={isLoaded()} fallback={t('Loading')}>
|
<Show when={isLoaded()} fallback={<Loading />}>
|
||||||
<SearchView results={props.searchResults || []} query={props.searchQuery} />
|
<SearchView results={props.searchResults || []} query={props.searchQuery} />
|
||||||
</Show>
|
</Show>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
|
|
|
@ -4,8 +4,8 @@ import type { PageProps } from '../types'
|
||||||
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||||
import { loadArticlesForTopics, resetSortedArticles } from '../../stores/zine/articles'
|
import { loadArticlesForTopics, resetSortedArticles } from '../../stores/zine/articles'
|
||||||
import { useRouter } from '../../stores/router'
|
import { useRouter } from '../../stores/router'
|
||||||
import { t } from '../../utils/intl'
|
|
||||||
import { loadTopic } from '../../stores/zine/topics'
|
import { loadTopic } from '../../stores/zine/topics'
|
||||||
|
import { Loading } from '../Loading'
|
||||||
|
|
||||||
export const TopicPage = (props: PageProps) => {
|
export const TopicPage = (props: PageProps) => {
|
||||||
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.authorArticles) && Boolean(props.author))
|
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.authorArticles) && Boolean(props.author))
|
||||||
|
@ -37,7 +37,7 @@ export const TopicPage = (props: PageProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainLayout>
|
<MainLayout>
|
||||||
<Show when={isLoaded()} fallback={t('Loading')}>
|
<Show when={isLoaded()} fallback={<Loading />}>
|
||||||
<TopicView topic={props.topic} topicArticles={props.topicArticles} topicSlug={slug()} />
|
<TopicView topic={props.topic} topicArticles={props.topicArticles} topicSlug={slug()} />
|
||||||
</Show>
|
</Show>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic-details__item {
|
.topicDetailsItem {
|
||||||
margin-bottom: 1.2rem;
|
margin-bottom: 1.2rem;
|
||||||
|
|
||||||
@include media-breakpoint-up(md) {
|
@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;
|
font-weight: bold;
|
||||||
@include font-size(1.7rem);
|
@include font-size(1.7rem);
|
||||||
|
|
||||||
margin-bottom: 0.8rem;
|
margin-bottom: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic__avatar {
|
.topicAvatar {
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
margin-right: 1.2rem;
|
margin-right: 1.2rem;
|
||||||
|
@ -41,7 +55,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic-description {
|
.topicDescription {
|
||||||
@include font-size(1.5rem);
|
@include font-size(1.5rem);
|
||||||
|
|
||||||
color: #696969;
|
color: #696969;
|
||||||
|
@ -52,14 +66,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic-description--short {
|
.topicDescriptionShort {
|
||||||
display: box;
|
display: block;
|
||||||
-webkit-line-clamp: 3;
|
-webkit-line-clamp: 3;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic-details {
|
.topicDetails {
|
||||||
@include font-size(1.7rem);
|
@include font-size(1.7rem);
|
||||||
|
|
||||||
color: #9fa1a7;
|
color: #9fa1a7;
|
||||||
|
@ -70,7 +84,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic-details__item {
|
.topicDetailsItem {
|
||||||
margin-right: 1.6rem;
|
margin-right: 1.6rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { capitalize, plural } from '../../utils'
|
import { capitalize, plural } from '../../utils'
|
||||||
import { Show } from 'solid-js/web'
|
import { Show } from 'solid-js/web'
|
||||||
import './Card.scss'
|
import style from './Card.module.scss'
|
||||||
import { createMemo } from 'solid-js'
|
import { createMemo } from 'solid-js'
|
||||||
import type { Topic } from '../../graphql/types.gen'
|
import type { Topic } from '../../graphql/types.gen'
|
||||||
import { FollowingEntity } from '../../graphql/types.gen'
|
import { FollowingEntity } from '../../graphql/types.gen'
|
||||||
|
@ -15,6 +15,9 @@ interface TopicProps {
|
||||||
subscribed?: boolean
|
subscribed?: boolean
|
||||||
shortDescription?: boolean
|
shortDescription?: boolean
|
||||||
subscribeButtonBottom?: boolean
|
subscribeButtonBottom?: boolean
|
||||||
|
additionalClass?: string
|
||||||
|
isTopicInRow?: boolean
|
||||||
|
iconButton?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TopicCard = (props: TopicProps) => {
|
export const TopicCard = (props: TopicProps) => {
|
||||||
|
@ -37,15 +40,21 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
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 }}>
|
<div classList={{ 'col-md-7': !props.compact && !props.subscribeButtonBottom }}>
|
||||||
<Show when={props.topic.title}>
|
<Show when={props.topic.title}>
|
||||||
<div class="topic-title">
|
<div class={style.topicTitle}>
|
||||||
<a href={`/topic/${props.topic.slug}`}>{capitalize(props.topic.title || '')}</a>
|
<a href={`/topic/${props.topic.slug}`}>{capitalize(props.topic.title || '')}</a>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={props.topic.pic}>
|
<Show when={props.topic.pic}>
|
||||||
<div class="topic__avatar">
|
<div class={style.topicAvatar}>
|
||||||
<a href={props.topic.slug}>
|
<a href={props.topic.slug}>
|
||||||
<img src={props.topic.pic} alt={props.topic.title} />
|
<img src={props.topic.pic} alt={props.topic.title} />
|
||||||
</a>
|
</a>
|
||||||
|
@ -53,15 +62,18 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={!props.compact && props.topic?.body}>
|
<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}
|
{props.topic.body}
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={props.topic?.stat}>
|
<Show when={props.topic?.stat}>
|
||||||
<div class="topic-details">
|
<div class={style.topicDetails}>
|
||||||
<Show when={!props.compact}>
|
<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 +
|
{props.topic.stat?.shouts +
|
||||||
' ' +
|
' ' +
|
||||||
t('post') +
|
t('post') +
|
||||||
|
@ -70,7 +82,7 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']
|
locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span class="topic-details__item" classList={{ compact: props.compact }}>
|
<span class={style.topicDetailsTtem} classList={{ compact: props.compact }}>
|
||||||
{props.topic.stat?.authors +
|
{props.topic.stat?.authors +
|
||||||
' ' +
|
' ' +
|
||||||
t('author') +
|
t('author') +
|
||||||
|
@ -79,7 +91,7 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']
|
locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span class="topic-details__item" classList={{ compact: props.compact }}>
|
<span class={style.topicDetailsItem} classList={{ compact: props.compact }}>
|
||||||
{props.topic.stat?.followers +
|
{props.topic.stat?.followers +
|
||||||
' ' +
|
' ' +
|
||||||
t('follower') +
|
t('follower') +
|
||||||
|
@ -118,12 +130,16 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
when={subscribed()}
|
when={subscribed()}
|
||||||
fallback={
|
fallback={
|
||||||
<button onClick={() => subscribe(true)} class="button--light">
|
<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>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<button onClick={() => subscribe(false)} class="button--light">
|
<button onClick={() => subscribe(false)} class="button--light">
|
||||||
- {t('Unfollow')}
|
<Show when={props.iconButton}>{/*<Icon name={}/>*/}</Show>
|
||||||
|
|
||||||
|
<Show when={!props.iconButton}>- {t('Unfollow')}</Show>
|
||||||
</button>
|
</button>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -31,6 +31,11 @@ export const AllAuthorsView = (props: Props) => {
|
||||||
|
|
||||||
const byLetter = createMemo<{ [letter: string]: Author[] }>(() => {
|
const byLetter = createMemo<{ [letter: string]: Author[] }>(() => {
|
||||||
return sortedAuthors().reduce((acc, author) => {
|
return sortedAuthors().reduce((acc, author) => {
|
||||||
|
if (!author.name) {
|
||||||
|
// name === null for new users
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
const letter = author.name[0].toUpperCase()
|
const letter = author.name[0].toUpperCase()
|
||||||
if (!acc[letter]) {
|
if (!acc[letter]) {
|
||||||
acc[letter] = []
|
acc[letter] = []
|
||||||
|
|
|
@ -9,8 +9,9 @@ import { useAuthorsStore } from '../../stores/zine/authors'
|
||||||
import { useArticlesStore } from '../../stores/zine/articles'
|
import { useArticlesStore } from '../../stores/zine/articles'
|
||||||
|
|
||||||
import '../../styles/Topic.scss'
|
import '../../styles/Topic.scss'
|
||||||
// import { useTopicsStore } from '../../stores/zine/topics'
|
import { useTopicsStore } from '../../stores/zine/topics'
|
||||||
import { useRouter } from '../../stores/router'
|
import { useRouter } from '../../stores/router'
|
||||||
|
import Beside from '../Feed/Beside'
|
||||||
|
|
||||||
// TODO: load reactions on client
|
// TODO: load reactions on client
|
||||||
type AuthorProps = {
|
type AuthorProps = {
|
||||||
|
@ -30,6 +31,7 @@ export const AuthorView = (props: AuthorProps) => {
|
||||||
sortedArticles: props.authorArticles
|
sortedArticles: props.authorArticles
|
||||||
})
|
})
|
||||||
const { authorEntities } = useAuthorsStore({ authors: [props.author] })
|
const { authorEntities } = useAuthorsStore({ authors: [props.author] })
|
||||||
|
const { topicsByAuthor } = useTopicsStore()
|
||||||
|
|
||||||
const author = createMemo(() => authorEntities()[props.authorSlug])
|
const author = createMemo(() => authorEntities()[props.authorSlug])
|
||||||
const { getSearchParams, changeSearchParam } = useRouter<AuthorPageSearchParams>()
|
const { getSearchParams, changeSearchParam } = useRouter<AuthorPageSearchParams>()
|
||||||
|
@ -54,21 +56,22 @@ export const AuthorView = (props: AuthorProps) => {
|
||||||
{t('Recent')}
|
{t('Recent')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li classList={{ selected: getSearchParams().by === 'rating' }}>
|
{/*TODO: server sort*/}
|
||||||
<button type="button" onClick={() => changeSearchParam('by', 'rating')}>
|
{/*<li classList={{ selected: getSearchParams().by === 'rating' }}>*/}
|
||||||
{t('Popular')}
|
{/* <button type="button" onClick={() => changeSearchParam('by', 'rating')}>*/}
|
||||||
</button>
|
{/* {t('Popular')}*/}
|
||||||
</li>
|
{/* </button>*/}
|
||||||
<li classList={{ selected: getSearchParams().by === 'viewed' }}>
|
{/*</li>*/}
|
||||||
<button type="button" onClick={() => changeSearchParam('by', 'viewed')}>
|
{/*<li classList={{ selected: getSearchParams().by === 'viewed' }}>*/}
|
||||||
{t('Views')}
|
{/* <button type="button" onClick={() => changeSearchParam('by', 'viewed')}>*/}
|
||||||
</button>
|
{/* {t('Views')}*/}
|
||||||
</li>
|
{/* </button>*/}
|
||||||
<li classList={{ selected: getSearchParams().by === 'commented' }}>
|
{/*</li>*/}
|
||||||
<button type="button" onClick={() => changeSearchParam('by', 'commented')}>
|
{/*<li classList={{ selected: getSearchParams().by === 'commented' }}>*/}
|
||||||
{t('Discussing')}
|
{/* <button type="button" onClick={() => changeSearchParam('by', 'commented')}>*/}
|
||||||
</button>
|
{/* {t('Discussing')}*/}
|
||||||
</li>
|
{/* </button>*/}
|
||||||
|
{/*</li>*/}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
|
@ -79,24 +82,33 @@ export const AuthorView = (props: AuthorProps) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="floor">
|
|
||||||
<h3 class="col-12">{title()}</h3>
|
<h3 class="col-12">{title()}</h3>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<Show when={sortedArticles().length > 0}>
|
<Show when={sortedArticles().length > 0}>
|
||||||
{/*FIXME*/}
|
<Beside
|
||||||
{/*<Beside*/}
|
title={t('Topics which supported by author')}
|
||||||
{/* title={t('Topics which supported by author')}*/}
|
values={topicsByAuthor()[author().slug].slice(0, 5)}
|
||||||
{/* values={getTopicsByAuthor()[author().slug].slice(0, 5)}*/}
|
beside={sortedArticles()[0]}
|
||||||
{/* beside={articles()[0]}*/}
|
wrapper={'topic'}
|
||||||
{/* wrapper={'topic'}*/}
|
topicShortDescription={true}
|
||||||
{/* topicShortDescription={true}*/}
|
isTopicCompact={true}
|
||||||
{/*/>*/}
|
isTopicInRow={true}
|
||||||
|
iconButton={true}
|
||||||
|
/>
|
||||||
<Row3 articles={sortedArticles().slice(1, 4)} />
|
<Row3 articles={sortedArticles().slice(1, 4)} />
|
||||||
|
|
||||||
|
<Show when={sortedArticles().length > 4}>
|
||||||
<Row2 articles={sortedArticles().slice(4, 6)} />
|
<Row2 articles={sortedArticles().slice(4, 6)} />
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<Show when={sortedArticles().length > 6}>
|
||||||
<Row3 articles={sortedArticles().slice(6, 9)} />
|
<Row3 articles={sortedArticles().slice(6, 9)} />
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<Show when={sortedArticles().length > 9}>
|
||||||
<Row3 articles={sortedArticles().slice(9, 12)} />
|
<Row3 articles={sortedArticles().slice(9, 12)} />
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -32,14 +32,6 @@ export const TopicView = (props: TopicProps) => {
|
||||||
|
|
||||||
const topic = createMemo(() => topicEntities()[props.topicSlug])
|
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 title = createMemo(() => {
|
||||||
const m = getSearchParams().by
|
const m = getSearchParams().by
|
||||||
if (m === 'viewed') return t('Top viewed')
|
if (m === 'viewed') return t('Top viewed')
|
||||||
|
@ -60,21 +52,22 @@ export const TopicView = (props: TopicProps) => {
|
||||||
{t('Recent')}
|
{t('Recent')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li classList={{ selected: getSearchParams().by === 'rating' }}>
|
{/*TODO: server sort*/}
|
||||||
<button type="button" onClick={() => changeSearchParam('by', 'rating')}>
|
{/*<li classList={{ selected: getSearchParams().by === 'rating' }}>*/}
|
||||||
{t('Popular')}
|
{/* <button type="button" onClick={() => changeSearchParam('by', 'rating')}>*/}
|
||||||
</button>
|
{/* {t('Popular')}*/}
|
||||||
</li>
|
{/* </button>*/}
|
||||||
<li classList={{ selected: getSearchParams().by === 'viewed' }}>
|
{/*</li>*/}
|
||||||
<button type="button" onClick={() => changeSearchParam('by', 'viewed')}>
|
{/*<li classList={{ selected: getSearchParams().by === 'viewed' }}>*/}
|
||||||
{t('Views')}
|
{/* <button type="button" onClick={() => changeSearchParam('by', 'viewed')}>*/}
|
||||||
</button>
|
{/* {t('Views')}*/}
|
||||||
</li>
|
{/* </button>*/}
|
||||||
<li classList={{ selected: getSearchParams().by === 'commented' }}>
|
{/*</li>*/}
|
||||||
<button type="button" onClick={() => changeSearchParam('by', 'commented')}>
|
{/*<li classList={{ selected: getSearchParams().by === 'commented' }}>*/}
|
||||||
{t('Discussing')}
|
{/* <button type="button" onClick={() => changeSearchParam('by', 'commented')}>*/}
|
||||||
</button>
|
{/* {t('Discussing')}*/}
|
||||||
</li>
|
{/* </button>*/}
|
||||||
|
{/*</li>*/}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
|
@ -92,7 +85,10 @@ export const TopicView = (props: TopicProps) => {
|
||||||
<For each={sortedArticles().slice(0, 6)}>
|
<For each={sortedArticles().slice(0, 6)}>
|
||||||
{(article) => (
|
{(article) => (
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<ArticleCard article={article} />
|
<ArticleCard
|
||||||
|
article={article}
|
||||||
|
settings={{ isFloorImportant: true, isBigTitle: true }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
|
@ -108,11 +104,11 @@ export const TopicView = (props: TopicProps) => {
|
||||||
beside={sortedArticles()[6]}
|
beside={sortedArticles()[6]}
|
||||||
wrapper={'author'}
|
wrapper={'author'}
|
||||||
/>
|
/>
|
||||||
<Row3 articles={sortedArticles().slice(6, 9)} />
|
<Row3 articles={sortedArticles().slice(7, 10)} />
|
||||||
<Row2 articles={sortedArticles().slice(9, 11)} />
|
<Row2 articles={sortedArticles().slice(10, 12)} />
|
||||||
<Row3 articles={sortedArticles().slice(11, 14)} />
|
<Row3 articles={sortedArticles().slice(12, 15)} />
|
||||||
<Row3 articles={sortedArticles().slice(14, 17)} />
|
<Row3 articles={sortedArticles().slice(15, 18)} />
|
||||||
<Row3 articles={sortedArticles().slice(17, 20)} />
|
<Row3 articles={sortedArticles().slice(18, 21)} />
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
@ -2,8 +2,8 @@ import { ClientOptions, dedupExchange, fetchExchange, createClient, Exchange } f
|
||||||
import { devtoolsExchange } from '@urql/devtools'
|
import { devtoolsExchange } from '@urql/devtools'
|
||||||
import { isDev } from '../utils/config'
|
import { isDev } from '../utils/config'
|
||||||
|
|
||||||
export const baseUrl = 'https://newapi.discours.io'
|
// export const baseUrl = 'https://newapi.discours.io'
|
||||||
// export const baseUrl = 'http://localhost:8000'
|
export const baseUrl = 'http://localhost:8000'
|
||||||
|
|
||||||
const exchanges: Exchange[] = [dedupExchange, fetchExchange]
|
const exchanges: Exchange[] = [dedupExchange, fetchExchange]
|
||||||
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { gql } from '@urql/core'
|
|
||||||
|
|
||||||
export default gql`
|
|
||||||
query ReactionsByShoutQuery($slug: String!, $limit: Int!, $offset: Int!) {
|
|
||||||
reactionsByShout(slug: $slug, limit: $limit, offset: $offset) {
|
|
||||||
id
|
|
||||||
body
|
|
||||||
createdAt
|
|
||||||
createdBy {
|
|
||||||
_id: slug
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
userpic
|
|
||||||
}
|
|
||||||
updatedAt
|
|
||||||
replyTo {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
kind
|
|
||||||
range
|
|
||||||
stat {
|
|
||||||
_id: viewed
|
|
||||||
viewed
|
|
||||||
reacted
|
|
||||||
rating
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -347,7 +347,6 @@ export type Query = {
|
||||||
markdownBody: Scalars['String']
|
markdownBody: Scalars['String']
|
||||||
myChats: Array<Maybe<ChatResult>>
|
myChats: Array<Maybe<ChatResult>>
|
||||||
reactionsByAuthor: Array<Maybe<Reaction>>
|
reactionsByAuthor: Array<Maybe<Reaction>>
|
||||||
reactionsByShout: Array<Maybe<Reaction>>
|
|
||||||
reactionsForShouts: Array<Maybe<Reaction>>
|
reactionsForShouts: Array<Maybe<Reaction>>
|
||||||
recentAll: Array<Maybe<Shout>>
|
recentAll: Array<Maybe<Shout>>
|
||||||
recentCandidates: Array<Maybe<Shout>>
|
recentCandidates: Array<Maybe<Shout>>
|
||||||
|
@ -422,12 +421,6 @@ export type QueryReactionsByAuthorArgs = {
|
||||||
slug: Scalars['String']
|
slug: Scalars['String']
|
||||||
}
|
}
|
||||||
|
|
||||||
export type QueryReactionsByShoutArgs = {
|
|
||||||
limit: Scalars['Int']
|
|
||||||
offset: Scalars['Int']
|
|
||||||
slug: Scalars['String']
|
|
||||||
}
|
|
||||||
|
|
||||||
export type QueryReactionsForShoutsArgs = {
|
export type QueryReactionsForShoutsArgs = {
|
||||||
limit: Scalars['Int']
|
limit: Scalars['Int']
|
||||||
offset: Scalars['Int']
|
offset: Scalars['Int']
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
"Comments": "Комментарии",
|
"Comments": "Комментарии",
|
||||||
"Communities": "Сообщества",
|
"Communities": "Сообщества",
|
||||||
"Create account": "Создать аккаунт",
|
"Create account": "Создать аккаунт",
|
||||||
|
"Copy link": "Скопировать ссылку",
|
||||||
"Delete": "Удалить",
|
"Delete": "Удалить",
|
||||||
"Discours": "Дискурс",
|
"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": "Дискурс — это интеллектуальная среда, веб-пространство и инструменты, которые позволяют авторам сотрудничать с читателями и объединяться для совместного создания публикаций и медиапроектов",
|
"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[]>([])
|
const warnings = atom<Warning[]>([])
|
||||||
|
|
||||||
export const showModal = (modalType: ModalType) => modal.set(modalType)
|
export const showModal = (modalType: ModalType) => modal.set(modalType)
|
||||||
|
|
||||||
export const hideModal = () => modal.set(null)
|
export const hideModal = () => modal.set(null)
|
||||||
|
export const toggleModal = (modalType) => modal.get() ? hideModal() : showModal(modalType)
|
||||||
|
|
||||||
export const clearWarns = () => warnings.set([])
|
export const clearWarns = () => warnings.set([])
|
||||||
export const warn = (warning: Warning) => warnings.set([...warnings.get(), warning])
|
export const warn = (warning: Warning) => warnings.set([...warnings.get(), warning])
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ body {
|
||||||
&.fixed {
|
&.fixed {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,7 +211,7 @@ button {
|
||||||
|
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
margin-top: 0.6rem;
|
margin-top: 0.6rem;
|
||||||
padding: 0.6rem 1.2rem 1rem 1rem;
|
padding: 0.6rem 1.2rem 0.6rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
form {
|
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 {
|
.floor--important {
|
||||||
background: #000;
|
background: #000;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: $grid-gutter-width 0;
|
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) {
|
@include media-breakpoint-down(md) {
|
||||||
margin-bottom: 5rem;
|
margin-bottom: 5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.all-materials,
|
.all-materials {
|
||||||
.shout-card__title {
|
|
||||||
a {
|
a {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shout__topic,
|
|
||||||
.shout__author {
|
|
||||||
a {
|
|
||||||
color: rgb(255 255 255 / 50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
color: #000 !important;
|
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 */
|
/* stylelint-disable-next-line */
|
||||||
astro-island {
|
astro-island {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
|
@ -533,7 +534,9 @@ astro-island {
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
flex: 1 100%;
|
flex: 1 100%;
|
||||||
|
min-height: 300px;
|
||||||
padding-top: 100px;
|
padding-top: 100px;
|
||||||
|
position: relative;
|
||||||
transition: all 1s ease;
|
transition: all 1s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ import type { Reaction, Shout, FollowingEntity, AuthResult } from '../graphql/ty
|
||||||
import { publicGraphQLClient } from '../graphql/publicGraphQLClient'
|
import { publicGraphQLClient } from '../graphql/publicGraphQLClient'
|
||||||
import { privateGraphQLClient } from '../graphql/privateGraphQLClient'
|
import { privateGraphQLClient } from '../graphql/privateGraphQLClient'
|
||||||
import articleBySlug from '../graphql/query/article-by-slug'
|
import articleBySlug from '../graphql/query/article-by-slug'
|
||||||
import articleReactions from '../graphql/query/article-reactions'
|
|
||||||
import articlesRecentAll from '../graphql/query/articles-recent-all'
|
import articlesRecentAll from '../graphql/query/articles-recent-all'
|
||||||
import articlesRecentPublished from '../graphql/query/articles-recent-published'
|
import articlesRecentPublished from '../graphql/query/articles-recent-published'
|
||||||
import topicsAll from '../graphql/query/topics-all'
|
import topicsAll from '../graphql/query/topics-all'
|
||||||
|
@ -281,14 +280,14 @@ export const apiClient = {
|
||||||
offset: number
|
offset: number
|
||||||
}): Promise<Reaction[]> => {
|
}): Promise<Reaction[]> => {
|
||||||
const response = await publicGraphQLClient
|
const response = await publicGraphQLClient
|
||||||
.query(articleReactions, {
|
.query(reactionsForShouts, {
|
||||||
slug: articleSlug,
|
shouts: [articleSlug],
|
||||||
limit,
|
limit,
|
||||||
offset
|
offset
|
||||||
})
|
})
|
||||||
.toPromise()
|
.toPromise()
|
||||||
|
|
||||||
return response.data?.reactionsByShout
|
return response.data?.reactionsForShouts
|
||||||
},
|
},
|
||||||
getAuthorsBySlugs: async ({ slugs }) => {
|
getAuthorsBySlugs: async ({ slugs }) => {
|
||||||
const response = await publicGraphQLClient.query(authorsBySlugs, { slugs }).toPromise()
|
const response = await publicGraphQLClient.query(authorsBySlugs, { slugs }).toPromise()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user