postmerge

This commit is contained in:
tonyrewin 2022-10-19 17:35:20 +03:00
commit c652c4ee84
51 changed files with 954 additions and 554 deletions

View File

@ -17,8 +17,10 @@
"lint:code:fix": "eslint . --fix",
"lint:styles": "stylelint **/*.{scss,css}",
"lint:styles:fix": "stylelint **/*.{scss,css} --fix",
"pre-commit": "lint-staged",
"pre-push": "npm run typecheck",
"pre-commit": "",
"pre-push": "",
"pre-commit-old": "lint-staged",
"pre-push-old": "npm run typecheck",
"prepare": "husky install",
"preview": "astro preview",
"server": "node server/server.mjs",

View 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

View 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

View 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

View File

@ -37,7 +37,6 @@ const formatDate = (date: Date) => {
}
export const FullArticle = (props: ArticleProps) => {
const body = createMemo(() => props.article?.body?.toString().trim())
const { session } = useAuthStore()
onMount(() => {
@ -93,9 +92,16 @@ export const FullArticle = (props: ArticleProps) => {
<div class="shout__cover" style={{ 'background-image': `url('${props.article.cover}')` }} />
</div>
<div class="shout__body">
<MD body={body()} />
</div>
<Show when={Boolean(props.article.body)}>
<div class="shout__body">
<Show
when={!props.article.body.startsWith('<')}
fallback={<div innerHTML={props.article.body} />}
>
<MD body={props.article.body} />
</Show>
</div>
</Show>
</article>
<div class="col-md-8 shift-content">

View File

@ -8,30 +8,30 @@
}
}
.author__details {
.authorDetails {
display: flex;
flex: 1;
padding-right: 1.2rem;
width: max-content;
}
.author__details-wrapper {
.authorDetailsWrapper {
flex: 1;
}
.author__name {
.authorName {
border: none !important;
font-size: 1.7rem;
font-weight: 500;
margin-bottom: 0.8rem;
}
.author__about {
.authorAbout {
font-size: 1.5rem;
color: rgb(0 0 0 / 60%);
}
.author__subscribe {
.authorSubscribe {
@include media-breakpoint-down(lg) {
padding: 0 0 0 42px;
}
@ -43,6 +43,7 @@
height: 32px;
margin-right: 0.4rem;
position: relative;
transition: background-color 0.2s;
vertical-align: middle;
width: 32px;
@ -57,8 +58,17 @@
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
transition: filter 0.2s;
width: 18px;
}
&:hover {
background: #000;
&::before {
filter: invert(0);
}
}
}
a[href*='facebook.com/'] {
@ -90,9 +100,15 @@
background-image: url(/icons/tumblr-white.svg);
}
}
a[href*='instagram.com/'] {
&::before {
background-image: url(/icons/instagram-white.svg);
}
}
}
.button--subscribe {
.buttonSubscribe {
align-items: center;
aspect-ratio: 1/1;
background: #f6f6f6;
@ -105,14 +121,15 @@
}
}
.button__label {
.buttonLabel {
display: none;
}
.button--write {
.buttonWrite {
background: #f7f7f7;
color: #000;
display: inline-flex;
@include font-size(1.5rem);
.icon {
@ -123,3 +140,62 @@
height: 15px;
}
}
.authorPage {
.authorName {
@include font-size(3.4rem);
font-weight: 500;
margin-bottom: 0.2em;
}
.authorAbout {
color: #696969;
@include font-size(1.7rem);
}
.authorSubscribe {
display: flex;
margin-top: 2rem;
}
.authorDetails {
display: block;
}
.buttonLabel {
display: block;
}
.buttonSubscribe {
aspect-ratio: auto;
background-color: #000;
border-radius: 2px;
float: none;
padding-bottom: 0.6rem;
padding-top: 0.6rem;
.icon {
margin-right: 0.5em;
img {
filter: invert(1);
}
}
&:hover {
.icon img {
filter: invert(0.7);
}
}
}
.buttonSubscribe img {
vertical-align: text-top;
}
.button {
margin-right: 1.6rem;
vertical-align: middle;
}
}

View File

@ -2,13 +2,14 @@ import { For, Show } from 'solid-js/web'
import type { Author } from '../../graphql/types.gen'
import Userpic from './Userpic'
import { Icon } from '../Nav/Icon'
import './Card.scss'
import style from './Card.module.scss'
import { createMemo } from 'solid-js'
import { translit } from '../../utils/ru2en'
import { t } from '../../utils/intl'
import { useAuthStore } from '../../stores/auth'
import { locale } from '../../stores/ui'
import { follow, unfollow } from '../../stores/zine/common'
import { clsx } from 'clsx'
interface AuthorCardProps {
compact?: boolean
@ -17,6 +18,7 @@ interface AuthorCardProps {
hasLink?: boolean
subscribed?: boolean
author: Author
isAuthorPage?: boolean
}
export const AuthorCard = (props: AuthorCardProps) => {
@ -34,45 +36,51 @@ export const AuthorCard = (props: AuthorCardProps) => {
}
// TODO: reimplement AuthorCard
return (
<div class="author">
<Userpic user={props.author} hasLink={props.hasLink} />
<div class={style.author} classList={{ [style.authorPage]: props.isAuthorPage }}>
<Userpic user={props.author} hasLink={props.hasLink} isBig={props.isAuthorPage} />
<div class="author__details">
<div class="author__details-wrapper">
<div class={style.authorDetails}>
<div class={style.authorDetailsWrapper}>
<Show when={props.hasLink}>
<a class="author__name text-3xl text-2xl" href={`/author/${props.author.slug}`}>
<a class={style.authorName} href={`/author/${props.author.slug}`}>
{name()}
</a>
</Show>
<Show when={!props.hasLink}>
<div class="author__name text-3xl text-2xl">{name()}</div>
<div class={style.authorName}>{name()}</div>
</Show>
<Show when={!props.hideDescription}>
<div class="author__about">{bio()}</div>
<div class={style.authorAbout}>{bio()}</div>
</Show>
</div>
<Show when={canFollow()}>
<div class="author__subscribe">
<div class={style.authorSubscribe}>
<Show
when={subscribed()}
fallback={
<button onClick={() => follow} class="button button--subscribe">
<Icon name="author-subscribe" />
<span class="button__label">+&nbsp;{t('Follow')}</span>
<button
onClick={() => follow}
class={clsx('button button--subscribe', style.button, style.buttonSubscribe)}
>
<Icon name="author-subscribe" class={style.icon} />
<span class={style.buttonLabel}>&nbsp;{t('Follow')}</span>
</button>
}
>
<button onClick={() => unfollow} class="button button--subscribe">
<Icon name="author-unsubscribe" />
<span class="button__label">-&nbsp;{t('Unfollow')}</span>
<button
onClick={() => unfollow}
class={clsx('button button--subscribe', style.button, style.buttonSubscribe)}
>
<Icon name="author-unsubscribe" class={style.icon} />
<span class={style.buttonLabel}>-&nbsp;{t('Unfollow')}</span>
</button>
</Show>
<Show when={!props.compact}>
<button class="button button--write">
<Icon name="edit" />
<button class={clsx(style.buttonWrite, style.button, 'button')}>
<Icon name="edit" class={style.icon} />
{t('Write')}
</button>

View File

@ -1,67 +1,6 @@
.user-details {
margin-bottom: 4.4rem;
padding: 0;
.author__name {
@include font-size(3.4rem);
font-weight: 500;
margin-bottom: 0.2em;
}
.author__about {
color: #696969;
@include font-size(1.7rem);
}
.circlewrap {
margin-right: 4.8rem;
max-width: 168px;
min-width: 168px;
height: 168px;
width: 168px;
}
.circlewrap .userpic {
font-size: 2em;
line-height: 168px;
width: 100%;
}
.author__subscribe {
margin-top: 2rem;
}
.author__details {
display: block;
}
.button--subscribe {
aspect-ratio: auto;
background-color: #000;
border-radius: 2px;
float: none;
padding-bottom: 0.6rem;
padding-top: 0.6rem;
}
.button__label {
display: block;
}
.button--subscribe .icon {
filter: invert(1);
margin-right: 0.5em;
}
.button--subscribe img {
vertical-align: text-top;
}
.button {
margin-right: 1.6rem;
vertical-align: middle;
}
}
.author-page {

View File

@ -7,7 +7,7 @@ export default (props: { author: Author }) => {
<div class="container">
<div class="row">
<div class="user-details">
<AuthorCard author={props.author} compact={false} />
<AuthorCard author={props.author} compact={false} isAuthorPage={true} />
</div>
</div>
</div>

View File

@ -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%;
}
}

View File

@ -1,10 +1,11 @@
import { Show } from 'solid-js/web'
import type { Author } from '../../graphql/types.gen'
import './Userpic.scss'
import style from './Userpic.module.scss'
interface UserpicProps {
user: Author
hasLink?: boolean
isBig?: boolean
}
export default (props: UserpicProps) => {
@ -15,7 +16,7 @@ export default (props: UserpicProps) => {
}
return (
<div class="circlewrap">
<div class={style.circlewrap} classList={{ [style.big]: props.isBig }}>
<Show when={props.hasLink}>
<a href={`/author/${props.user.slug}`}>
<Show
@ -28,7 +29,7 @@ export default (props: UserpicProps) => {
/>
}
>
<div class="userpic">{letters()}</div>
<div class={style.userpic}>{letters()}</div>
</Show>
</a>
</Show>
@ -44,7 +45,7 @@ export default (props: UserpicProps) => {
/>
}
>
<div class="userpic">{letters()}</div>
<div class={style.userpic}>{letters()}</div>
</Show>
</Show>
</div>

View File

@ -1,24 +1,24 @@
// import menu from './extension/menu'
// import scroll from './prosemirror/extension/scroll'
import { keymap } from 'prosemirror-keymap'
import type { ProseMirrorExtension } from './helpers'
import { Schema } from 'prosemirror-model'
import type { Command } from 'prosemirror-state'
import { t } from '../../../utils/intl'
import base from './extension/base'
import code from './extension/code'
import collab from './extension/collab'
import dragHandle from './extension/drag-handle'
import image from './extension/image'
import link from './extension/link'
import markdown from './extension/markdown'
import pasteMarkdown from './extension/paste-markdown'
import placeholder from './extension/placeholder'
import selectionMenu from './extension/selection'
import strikethrough from './extension/strikethrough'
import table from './extension/table'
import todoList from './extension/todo-list'
import collab from './extension/collab'
import type { Config, YOptions } from '../store/context'
import type { ProseMirrorExtension } from './helpers'
import selectionMenu from './extension/selection'
import type { Command } from 'prosemirror-state'
import placeholder from './extension/placeholder'
import todoList from './extension/todo-list'
import strikethrough from './extension/strikethrough'
interface ExtensionsProps {
data?: unknown

View File

@ -1,4 +1,4 @@
.beside-column {
.besideColumn {
counter-reset: item;
list-style-type: none;
padding-left: 0;
@ -29,17 +29,6 @@
text-align: center;
width: 2em;
}
.shout-card--short {
font-size: 1.4rem;
margin-bottom: 0;
.shout-card__title,
.shout-card__subtitle {
display: inline;
@include font-size(1.4rem);
}
}
}
}
@ -51,7 +40,7 @@
}
}
.beside-column-title {
.besideColumnTitle {
align-items: baseline;
justify-content: space-between;
display: flex;
@ -87,14 +76,14 @@
}
}
.beside-column__topic {
.besideColumnTopic {
font-size: 1.2rem;
letter-spacing: 0.08em;
margin-bottom: 0.4rem;
text-transform: uppercase;
}
.beside-column__shout {
.besideColumnShout {
font-size: 1.4rem;
h4 {

View File

@ -4,7 +4,7 @@ import { For, Show } from 'solid-js/web'
import { ArticleCard } from './Card'
import { AuthorCard } from '../Author/Card'
import { TopicCard } from '../Topic/Card'
import './Beside.scss'
import style from './Beside.module.scss'
import type { Author, Shout, Topic, User } from '../../graphql/types.gen'
import { Icon } from '../Nav/Icon'
import { t } from '../../utils/intl'
@ -15,8 +15,10 @@ interface BesideProps {
beside: Shout
wrapper: 'topic' | 'author' | 'article' | 'top-article'
isTopicCompact?: boolean
isTopicInRow?: boolean
topicShortDescription?: boolean
topicsBySlug?: { [slug: string]: Topic }
iconButton?: boolean
}
export default (props: BesideProps) => {
@ -27,7 +29,7 @@ export default (props: BesideProps) => {
<Show when={!!props.values}>
<div class="col-md-4">
<Show when={!!props.title}>
<div class="beside-column-title">
<div class={style.besideColumnTitle}>
<h4>{props.title}</h4>
<Show when={props.wrapper === 'author'}>
@ -38,15 +40,17 @@ export default (props: BesideProps) => {
</Show>
</div>
</Show>
<ul class="beside-column">
<ul class={style.besideColumn}>
<For each={[...props.values]}>
{(value: Shout | User | Topic | Author) => (
<li classList={{ top: props.wrapper.startsWith('top-') }}>
<li classList={{ [style.top]: props.wrapper.startsWith('top-') }}>
<Show when={props.wrapper === 'topic'}>
<TopicCard
topic={value as Topic}
compact={props.isTopicCompact}
shortDescription={props.topicShortDescription}
isTopicInRow={props.isTopicInRow}
iconButton={props.iconButton}
/>
</Show>
<Show when={props.wrapper === 'author'}>
@ -58,7 +62,7 @@ export default (props: BesideProps) => {
<Show when={props.wrapper === 'top-article' && value?.slug}>
<ArticleCard
article={value as Shout}
settings={{ noimage: true, noauthor: true, nodate: true }}
settings={{ noimage: true, noauthor: true, nodate: true, isShort: true }}
/>
</Show>
</li>
@ -68,7 +72,7 @@ export default (props: BesideProps) => {
</div>
</Show>
<div class="col-md-8">
<ArticleCard article={props.beside} settings={{}} />
<ArticleCard article={props.beside} settings={{ isBigTitle: true }} />
</div>
</div>
</div>

View File

@ -1,22 +1,12 @@
.shout-card {
.shoutCard {
display: flex;
flex-direction: column;
line-height: 1.2;
margin-bottom: 2.4rem;
position: relative;
.floor--1 &:nth-child(2) {
border-top: 1px solid #141414;
margin-top: 2.4rem !important;
padding-top: 2.4rem;
.shout-card__cover {
display: none;
}
}
&:hover {
.shout-card__cover img {
.shoutCardCover img {
transform: scale(1.05);
}
}
@ -26,7 +16,13 @@
}
}
.shout-card__titles-container {
.shoutCardWithBorder {
border-top: 1px solid #141414;
margin-top: 2.4rem !important;
padding-top: 2.4rem;
}
.shoutCardTitlesContainer {
a {
&::before {
content: '';
@ -39,7 +35,7 @@
}
&:hover {
.shout-card__title .shout-card__link-container {
.shoutCardTitle .shoutCardLinkContainer {
background-color: #000;
color: #fff;
}
@ -47,11 +43,11 @@
}
}
.shout-card__cover-container {
.shoutCardCoverContainer {
position: relative;
}
.shout-card__cover {
.shoutCardCover {
height: 0;
margin-bottom: 1.6rem;
overflow: hidden;
@ -73,25 +69,19 @@
}
}
.shout__topic,
.shout__author {
a {
position: relative;
z-index: 2;
}
}
.shout__author,
.shout__date {
.shoutAuthor,
.shoutDate {
@include font-size(1.2rem);
}
.shout__author {
.shoutAuthor {
margin-right: 1.6rem;
a {
border: none;
color: rgb(0 0 0 / 70%);
position: relative;
z-index: 2;
&:hover {
color: #fff;
@ -99,15 +89,15 @@
}
}
.shout__date {
.shoutDate {
color: rgb(0 0 0 / 50%);
}
.shout__details {
.shoutDetails {
display: flex;
}
.shout-card__title {
.shoutCardTitle {
@include font-size(2.2rem);
font-weight: 700;
@ -115,29 +105,33 @@
margin-bottom: 0.8rem;
}
.shout-card__title,
.shout-card__subtitle {
.shoutCardTitle,
.shoutCardSubtitle {
&,
.shout-card__link-container {
.shoutCardLinkContainer {
box-decoration-break: clone;
/* stylelint-disable-next-line */
-webkit-box-decoration-break: clone;
}
}
.shout-card__subtitle {
.shoutCardSubtitle {
color: #696969;
@include font-size(1.7rem);
font-weight: 400;
line-height: 1.3;
margin-bottom: 0.8rem;
transition: color 0.2s, background-color 0.2s, box-shadow 0.2s;
}
.shout-card__link-container {
.shoutCardLinkContainer {
position: relative;
transition: color 0.2s, background-color 0.2s, box-shadow 0.2s;
}
.shout-card__edit-control {
.shoutCardEditControl {
border-radius: 2em;
min-height: 2.6em;
padding: 0 1.4em;
@ -149,17 +143,17 @@
}
}
.shout-card.withcover {
.shoutCard.withcover {
padding: 2.4rem;
&,
a,
.shout-card__title,
.shout-card__subtitle {
.shoutCardTitle,
.shoutCardSubtitle {
color: #fff;
}
.shout-card__cover {
.shoutCardCover {
height: 100%;
left: 0;
padding: 0;
@ -178,7 +172,7 @@
}
}
.shout-card__type {
.shoutCardType {
background: #fff;
border-radius: 100%;
height: 3.2rem;
@ -213,7 +207,7 @@
}
}
.shout-card__content {
.shoutCardContent {
margin-bottom: 1em;
@include media-breakpoint-up(md) {
@ -229,7 +223,7 @@
}
.col-md-6 .col-md-6 {
.shout-card {
.shoutCard {
border-bottom: 1px solid rgb(255 255 255 / 20%);
margin: 3.6rem 0 0;
padding-bottom: 2rem;
@ -244,71 +238,56 @@
}
}
.shout-card__cover-container {
.shoutCardCoverContainer {
display: none;
}
.shout-card__title,
.shout-card__subtitle {
.shoutCardTitle,
.shoutCardSubtitle {
display: inline;
@include font-size(2.6rem);
line-height: 1.2;
}
.shout-card__subtitle {
.shoutCardSubtitle {
color: #fff;
}
.shout__author {
.shoutAuthor {
margin-top: 0.6em;
}
}
}
.floor--important {
padding-bottom: $container-padding-x;
padding-top: $container-padding-x;
.shoutCardFloorImportant {
margin-bottom: $grid-gutter-width;
@include media-breakpoint-up(md) {
padding-bottom: $grid-gutter-width;
padding-top: $grid-gutter-width;
}
h2 {
@include font-size(4.4rem);
text-align: center;
}
.shout-card {
margin-bottom: $grid-gutter-width;
}
.shout__author {
.shoutAuthor {
margin-top: 0.8rem;
}
.col-md-6 {
.shout-card__title,
.shout-card__subtitle {
display: inline;
@include font-size(2.6rem);
line-height: 1.2;
}
.shout-card__title {
margin-right: 0.3em;
a {
color: rgb(255 255 255 / 50%);
}
}
.shout-card__titles-container {
.shoutCardTitle {
a {
color: #fff;
}
}
a:hover {
background: #fff;
color: #000 !important;
}
.shoutCardTitlesContainer {
a {
color: #fff;
&:hover {
.shout-card__title .shout-card__link-container {
.shoutCardTitle .shoutCardLinkContainer {
background-color: #fff;
color: #000;
}
@ -317,7 +296,7 @@
}
}
.shout-card--with-cover {
.shoutCardWithCover {
padding: 56.2% 2.4rem 0;
@include media-breakpoint-down(sm) {
@ -325,7 +304,7 @@
}
&.swiper-slide {
.shout-card__content {
.shoutCardContent {
@include media-breakpoint-down(md) {
padding-left: 10%;
}
@ -334,25 +313,25 @@
&,
a,
.shout-card__title,
.shout-card__subtitle {
.shoutCardTitle,
.shoutCardSubtitle {
color: #fff !important;
}
.shout__author,
.shout-card__titles-container {
.shoutAuthor,
.shoutCardTitlesContainer {
a:hover {
&,
.shout-card__title .shout-card__link-container {
.shoutCardTitle .shoutCardLinkContainer {
background-color: #fff;
color: #000 !important;
}
}
}
.shout-card__cover-container,
.shout-card__cover,
.shout-card__content {
.shoutCardCoverContainer,
.shoutCardCover,
.shoutCardContent {
height: 100%;
left: 0;
margin: 0;
@ -362,7 +341,7 @@
z-index: -1;
}
.shout-card__content {
.shoutCardContent {
display: flex;
flex-direction: column;
justify-content: end;
@ -370,7 +349,7 @@
z-index: 1;
}
.shout-card__cover {
.shoutCardCover {
padding: 0;
&::after {
@ -383,43 +362,37 @@
}
}
.shout-card__title {
.shoutCardTitle {
@include font-size(3.2rem);
}
}
.shout-card--content-top {
.shout-card__content {
.shoutCardContentTop {
.shoutCardContent {
justify-content: start;
}
}
.shout-card--short {
.shout-card__title {
@include font-size(1.7rem);
}
}
.shout-card--photo-bottom {
.shout-card__content {
.shoutCardPhotoBottom {
.shoutCardContent {
margin-bottom: 1.6rem;
}
.shout-card__cover-container {
.shoutCardCoverContainer {
order: 2;
}
}
.shout-card--feed {
.shoutCardFeed {
border-bottom: 1px solid #e8e8e8;
margin-bottom: 2em;
.shout-card__content {
.shoutCardContent {
margin-bottom: 0;
}
}
.shout-card__details {
.shoutCardDetails {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
@ -431,11 +404,11 @@
z-index: 2;
}
.shout-card__details-content {
.shoutCardDetailsContent {
display: flex;
}
.shout-card__details-item {
.shoutCardDetailsTtem {
align-items: center;
display: flex;
margin-right: 1.7em;
@ -454,7 +427,7 @@
}
}
.shout-card__comments {
.shoutCardComments {
align-items: center;
display: flex;
@ -476,12 +449,12 @@
display: flex;
}
.rating__value {
.ratingValue {
font-weight: bold;
margin: 0 0.5em;
}
.rating__control {
.ratingControl {
align-items: center;
border: 2px solid;
border-radius: 100%;
@ -501,32 +474,18 @@
}
}
.floor--7 {
@include media-breakpoint-down(md) {
margin-bottom: 1em;
.shoutCardVertical {
aspect-ratio: auto;
height: 100%;
margin: 1.6rem 0;
padding: 0 0 20%;
@include media-breakpoint-up(md) {
aspect-ratio: 1 / 1.6;
padding: 0;
}
.col-md-6 {
margin-bottom: 1.6em;
@include media-breakpoint-down(md) {
margin-right: 0;
}
}
.shout-card {
aspect-ratio: auto;
height: 100%;
margin: 1.6rem 0;
padding: 0 0 20%;
@include media-breakpoint-up(md) {
aspect-ratio: 1 / 1.6;
padding: 0;
}
}
.shout-card__title {
.shoutCardTitle {
font-size: 2.6rem !important;
@include media-breakpoint-between(lg, xl) {
@ -534,11 +493,11 @@
}
}
.shout-card__content {
.shoutCardContent {
justify-content: start;
position: relative;
.shout-card__subtitle {
.shoutCardSubtitle {
@include media-breakpoint-up(lg) {
@include font-size(2rem);
}
@ -552,44 +511,151 @@
.floor--9 {
/* TODO: refactor these styles */
.shout-card__title,
.shout-card__subtitle {
.shoutCardTitle,
.shoutCardSubtitle {
// display: inline;
font-size: 2.2rem;
}
.shout-card__title {
.shoutCardTitle {
padding-right: 0.25em;
}
.shout__author,
.shout__date {
.shoutAuthor,
.shoutDate {
margin-top: 0.8rem;
}
.beside-column .shout-card__titles-container {
.beside-column .shoutCardTitlesContainer {
position: relative;
a:hover {
.shout-card__link-container {
.shoutCardLinkContainer {
color: #fff;
}
}
}
}
.col-md-6,
.col-lg-6,
.col-md-8 {
.shout-card__title {
.shoutCardBigTitle {
.shoutCardTitle {
display: block;
@include font-size(3.2rem);
padding-right: 0;
}
.shout-card__subtitle {
.shoutCardSubtitle {
color: #696969;
@include font-size(2.4rem);
}
}
.shoutCardCompact {
.shoutCardTitle,
.shoutCardSubtitle {
display: inline;
@include font-size(2.6rem);
line-height: 1.2;
}
.shoutCardSubtitle {
padding-left: 0.3em;
box-decoration-break: slice;
/* stylelint-disable-next-line */
-webkit-box-decoration-break: slice;
}
a {
color: #fff;
&:hover {
.shoutCardSubtitle {
background-color: #fff;
color: #000;
}
}
}
}
.shoutCardShort {
font-size: 1.4rem;
margin-bottom: 0;
.shoutCardTitle,
.shoutCardSubtitle {
display: inline;
@include font-size(1.4rem);
}
.shoutCardTitle {
padding-right: 0.25em;
}
a:hover {
.shoutCardLinkContainer {
color: #fff;
}
}
}
.shoutCardSingle {
@include media-breakpoint-up(md) {
flex-direction: row;
.shoutCardTitlesContainer {
flex: 1;
}
.shoutCardCover {
margin-bottom: 0;
}
.shoutCardCoverContainer {
flex: 1 58.3333%;
}
.shoutCardContent {
display: flex;
flex-direction: column;
flex: 1 41.6666%;
justify-content: space-between;
margin-bottom: 0;
padding-left: 4rem;
}
.shoutTopic {
margin-bottom: 3.2rem;
}
.shoutCardTitle {
margin-bottom: 2.4rem;
}
.shoutAuthor {
align-items: end;
display: flex;
flex: 1;
}
}
.shoutCardType {
top: 0;
}
.shoutCardTitle {
@include font-size(4rem);
font-weight: 900;
line-height: 1.1;
}
.shoutCardSubtitle {
color: #696969;
flex: 1;
@include font-size(2.4rem);
}
}

View File

@ -5,9 +5,11 @@ import type { Shout } from '../../graphql/types.gen'
import { capitalize } from '../../utils'
import { translit } from '../../utils/ru2en'
import { Icon } from '../Nav/Icon'
import './Card.scss'
import style from './Card.module.scss'
import { locale } from '../../stores/ui'
import { handleClientRouteLinkClick } from '../../stores/router'
import { clsx } from 'clsx'
import CardTopic from './CardTopic'
interface ArticleCardProps {
settings?: {
@ -20,6 +22,14 @@ interface ArticleCardProps {
photoBottom?: boolean
additionalClass?: string
isFeedMode?: boolean
isFloorImportant?: boolean
isWithCover?: boolean
isBigTitle?: boolean
isVertical?: boolean
isShort?: boolean
withBorder?: boolean
isCompact?: boolean
isSingle?: boolean
}
article: Shout
}
@ -62,56 +72,65 @@ export const ArticleCard = (props: ArticleCardProps) => {
return (
<section
class={`shout-card ${props.settings?.additionalClass || ''}`}
class={clsx(style.shoutCard, `${props.settings?.additionalClass || ''}`)}
classList={{
'shout-card--short': props.settings?.noimage,
'shout-card--photo-bottom': props.settings?.noimage && props.settings?.photoBottom,
'shout-card--feed': props.settings?.isFeedMode
[style.shoutCardShort]: props.settings?.isShort,
[style.shoutCardPhotoBottom]: props.settings?.noimage && props.settings?.photoBottom,
[style.shoutCardFeed]: props.settings?.isFeedMode,
[style.shoutCardFloorImportant]: props.settings?.isFloorImportant,
[style.shoutCardWithCover]: props.settings?.isWithCover,
[style.shoutCardBigTitle]: props.settings?.isBigTitle,
[style.shoutCardVertical]: props.settings?.isVertical,
[style.shoutCardWithBorder]: props.settings?.withBorder,
[style.shoutCardCompact]: props.settings?.isCompact,
[style.shoutCardSingle]: props.settings?.isSingle
}}
>
<Show when={!props.settings?.noimage && cover}>
<div class="shout-card__cover-container">
<div class="shout-card__cover">
<div class={style.shoutCardCoverContainer}>
<div class={style.shoutCardCover}>
<img src={cover || ''} alt={title || ''} loading="lazy" />
</div>
</div>
</Show>
<div class="shout-card__content">
<div class={style.shoutCardContent}>
<Show when={layout && layout !== 'article' && !(props.settings?.noicon || props.settings?.noimage)}>
<div class="shout-card__type">
<div class={style.shoutCardType}>
<a href={`/topic/${mainTopic.slug}`}>
<Icon name={layout} />
<Icon name={layout} class={style.icon} />
</a>
</div>
</Show>
<Show when={!props.settings?.isGroup}>
<div class="shout__topic">
<a href={`/topic/${mainTopic.slug}`}>
{locale() === 'ru' && mainTopic.title ? mainTopic.title : mainTopic.slug.replace('-', ' ')}
</a>
</div>
<CardTopic
title={
locale() === 'ru' && mainTopic.title ? mainTopic.title : mainTopic.slug.replace('-', ' ')
}
slug={mainTopic.slug}
isFloorImportant={props.settings?.isFloorImportant}
/>
</Show>
<div class="shout-card__titles-container">
<div class={style.shoutCardTitlesContainer}>
<a href={`/${slug || ''}`} onClick={handleClientRouteLinkClick}>
<div class="shout-card__title">
<span class="shout-card__link-container">{title}</span>
<div class={style.shoutCardTitle}>
<span class={style.shoutCardLinkContainer}>{title}</span>
</div>
<Show when={!props.settings?.nosubtitle && subtitle}>
<div class="shout-card__subtitle">
<span class="shout-card__link-container">{subtitle}</span>
<div class={style.shoutCardSubtitle}>
<span class={style.shoutCardLinkContainer}>{subtitle}</span>
</div>
</Show>
</a>
</div>
<Show when={!props.settings?.noauthor || !props.settings?.nodate}>
<div class="shout__details">
<div class={style.shoutDetails}>
<Show when={!props.settings?.noauthor}>
<div class="shout__author">
<div class={style.shoutAuthor}>
<For each={authors}>
{(author, index) => {
const name =
@ -131,39 +150,39 @@ export const ArticleCard = (props: ArticleCardProps) => {
</Show>
<Show when={!props.settings?.nodate}>
<div class="shout__date">{formattedDate()}</div>
<div class={style.shoutDate}>{formattedDate()}</div>
</Show>
</div>
</Show>
<Show when={props.settings?.isFeedMode}>
<section class="shout-card__details">
<div class="shout-card__details-content">
<div class="shout-card__details-item rating">
<section class={style.shoutCardDetails}>
<div class={style.shoutCardDetailsContent}>
<div class={clsx(style.shoutCardDetailsItem, 'rating')}>
<button class="rating__control">&minus;</button>
<span class="rating__value">{stat?.rating || ''}</span>
<button class="rating__control">+</button>
</div>
<div class="shout-card__details-item shout-card__comments">
<Icon name="eye" />
<div class={clsx(style.shoutCardDetailsItem, style.shoutCardComments)}>
<Icon name="eye" class={style.icon} />
{stat?.viewed}
</div>
<div class="shout-card__details-item shout-card__comments">
<div class={clsx(style.shoutCardDetailsTtem, style.shoutCardComments)}>
<a href={`/${slug + '#comments' || ''}`}>
<Icon name="comment" />
<Icon name="comment" class={style.icon} />
{stat?.commented || ''}
</a>
</div>
<div class="shout-card__details-item">
<div class={style.shoutCardDetailsItem}>
<button>
<Icon name="bookmark" />
<Icon name="bookmark" class={style.icon} />
</button>
</div>
<div class="shout-card__details-item">
<div class={style.shoutCardDetailsItem}>
<button>
<Icon name="ellipsis" />
<Icon name="ellipsis" class={style.icon} />
</button>
</div>
</div>

View 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%);
}
}

View 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>
)
}

View File

@ -33,3 +33,13 @@
transition: filter 0.2s;
}
}
.floor--group {
@include media-breakpoint-down(sm) {
.col-lg-6 {
.row {
margin: 0 0 0 divide(-$container-padding-x, 2);
}
}
}
}

View File

@ -12,13 +12,16 @@ interface GroupProps {
export default (props: GroupProps) => {
if (!props.articles) props.articles = []
return (
<div class="floor floor--important">
<div class="floor floor--important floor--group">
<Show when={props.articles.length > 4}>
<div class="wide-container row">
<div class="group__header col-12">{props.header}</div>
<div class="col-lg-6">
<ArticleCard article={props.articles[0]} settings={{ nosubtitle: false, noicon: true }} />
<ArticleCard
article={props.articles[0]}
settings={{ nosubtitle: false, noicon: true, isFloorImportant: true, isBigTitle: true }}
/>
</div>
<div class="col-lg-6">
@ -28,7 +31,10 @@ export default (props: GroupProps) => {
{(a) => (
<div class="row">
<div class="col-md-8">
<ArticleCard article={a} settings={{ nosubtitle: false, noicon: true }} />
<ArticleCard
article={a}
settings={{ nosubtitle: false, noicon: true, isBigTitle: true }}
/>
</div>
</div>
)}
@ -37,12 +43,34 @@ export default (props: GroupProps) => {
<Show when={props.articles.length >= 4}>
<div class="col-md-6">
<For each={props.articles.slice(1, 3)}>
{(a) => <ArticleCard article={a} settings={{ noicon: true, noimage: true }} />}
{(a) => (
<ArticleCard
article={a}
settings={{
noicon: true,
noimage: true,
isBigTitle: true,
isCompact: true,
isFloorImportant: true
}}
/>
)}
</For>
</div>
<div class="col-md-6">
<For each={props.articles.slice(3, 5)}>
{(a) => <ArticleCard article={a} settings={{ noicon: true, noimage: true }} />}
{(a) => (
<ArticleCard
article={a}
settings={{
noicon: true,
noimage: true,
isBigTitle: true,
isCompact: true,
isFloorImportant: true
}}
/>
)}
</For>
</div>
</Show>

View File

@ -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);
}
}

View File

@ -1,14 +1,13 @@
import { Show } from 'solid-js'
import type { Shout } from '../../graphql/types.gen'
import { ArticleCard } from './Card'
import './Row1.scss'
export default (props: { article: Shout }) => (
<Show when={!!props.article}>
<div class="floor floor--one-article">
<div class="wide-container row">
<div class="col-12">
<ArticleCard article={props.article} />
<ArticleCard article={props.article} settings={{isSingle: true}} />
</div>
</div>
</div>

View File

@ -21,12 +21,7 @@ export default (props: { articles: Shout[] }) => {
return (
<Show when={!!a}>
<div class={`col-md-${x[y()][i()]}`}>
<ArticleCard
article={a}
settings={{
additionalClass: x[y()][i()] === '8' ? 'shout-card--with-cover' : ''
}}
/>
<ArticleCard article={a} settings={{ isWithCover: x[y()][i()] === '8' }} />
</div>
</Show>
)

View File

@ -7,14 +7,14 @@ export const Row5 = (props: { articles: Shout[] }) => {
<div class="wide-container row">
<div class="col-md-3">
<ArticleCard article={props.articles[0]} />
<ArticleCard article={props.articles[1]} />
<ArticleCard article={props.articles[1]} settings={{ noimage: true, withBorder: true }} />
</div>
<div class="col-md-6">
<ArticleCard article={props.articles[2]} />
<ArticleCard article={props.articles[2]} settings={{ isBigTitle: true }} />
</div>
<div class="col-md-3">
<ArticleCard article={props.articles[3]} />
<ArticleCard article={props.articles[4]} />
<ArticleCard article={props.articles[4]} settings={{ noimage: true, withBorder: true }} />
</div>
</div>
</div>

View File

@ -10,7 +10,12 @@ export default (props: { articles: Shout[] }) => (
<div class="col-md-6 col-lg-3">
<ArticleCard
article={a}
settings={{ additionalClass: 'shout-card--with-cover shout-card--content-top' }}
settings={{
additionalClass: 'shout-card--content-top',
isWithCover: true,
isBigTitle: true,
isVertical: true
}}
/>
</div>
)}

View File

@ -1,17 +1,3 @@
.floor--slider {
.shout-card {
.shout-card__cover-container {
z-index: 1;
}
.shout-card__cover {
&::after {
background: linear-gradient(0deg, rgb(0 0 0 / 80%) 0%, rgb(0 0 0 / 0%) 75%);
}
}
}
}
.swiper-slide {
height: 0 !important;
min-height: 0 !important;

View File

@ -60,7 +60,12 @@ export default (props: SliderProps) => {
{(a: Shout) => (
<ArticleCard
article={a}
settings={{ additionalClass: 'shout-card--with-cover swiper-slide' }}
settings={{
additionalClass: 'swiper-slide',
isFloorImportant: true,
isWithCover: true,
nodate: true
}}
/>
)}
</For>

View 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;
}

View File

@ -0,0 +1,9 @@
import styles from './Loading.module.scss'
export const Loading = () => {
return (
<div class={styles.container}>
<div class={styles.icon} />
</div>
)
}

View File

@ -9,6 +9,7 @@ import { useAuthStore, signIn, register } from '../../stores/auth'
import { useValidator } from '../../utils/validators'
import { baseUrl } from '../../graphql/publicGraphQLClient'
import { ApiError } from '../../utils/apiClient'
import { handleClientRouteLinkClick } from '../../stores/router'
type AuthMode = 'sign-in' | 'sign-up' | 'forget' | 'reset' | 'resend' | 'password'
@ -30,9 +31,16 @@ const titles = {
// const isProperEmail = (email) => email && email.length > 5 && email.includes('@') && email.includes('.')
// 3rd party provider auth handler
const oauth = (provider: string): void => {
const popup = window.open(`${baseUrl}/oauth/${provider}`, provider, 'width=740, height=420')
popup?.focus()
hideModal()
}
// FIXME !!!
// eslint-disable-next-line sonarjs/cognitive-complexity
export default (props: { code?: string; mode?: string }) => {
export default (props: { code?: string; mode?: AuthMode }) => {
const { session } = useAuthStore()
const [handshaking] = createSignal(false)
const { getModal } = useModalStore()
@ -45,13 +53,6 @@ export default (props: { code?: string; mode?: string }) => {
let passElement: HTMLInputElement | undefined
let codeElement: HTMLInputElement | undefined
// 3rd party provider auth handler
const oauth = (provider: string): void => {
const popup = window.open(`${baseUrl}/oauth/${provider}`, provider, 'width=740, height=420')
popup?.focus()
hideModal()
}
// FIXME: restore logic
// const usedEmails = {}
// const checkEmailAsync = async (email: string) => {
@ -185,9 +186,15 @@ export default (props: { code?: string; mode?: string }) => {
{t('New stories every day and even more!')}
</p>
<p class="disclamer">
{t('By signing up you agree with our')}
<a href="/about/terms-of-use" onClick={hideModal}>
{' ' + t('terms of use')}
{t('By signing up you agree with our')}{' '}
<a
href="/about/terms-of-use"
onClick={(event) => {
hideModal()
handleClientRouteLinkClick(event)
}}
>
{t('terms of use')}
</a>
, {t('personal data usage and email notifications')}.
</p>

View File

@ -20,6 +20,10 @@
}
}
.mainHeaderInner {
position: relative;
}
.headerFixed.headerScrolledBottom,
.headerFixed.headerScrolledTop {
.mainLogo {
@ -31,9 +35,26 @@
}
}
.popupShare {
opacity: 1;
transition: opacity 0.3s;
z-index: 1;
.headerScrolledTop & {
opacity: 0;
transition: opacity 0.3s, z-index 0s 0.3s;
z-index: -1;
}
}
.headerFixed {
position: fixed;
top: 0;
.fixed & {
bottom: 0;
margin-bottom: 0;
}
}
.headerInner {
@ -44,8 +65,7 @@
justify-content: space-between;
margin: 0;
&.fixed {
border-bottom: 4px solid #000;
.fixed & {
left: 0;
position: fixed;
right: 0;
@ -62,25 +82,33 @@
.mainLogo {
align-items: center;
display: inline-flex;
height: 70px;
height: 56px;
padding: 0 $container-padding-x 0 0;
position: relative;
transition: height 0.2s;
text-align: center;
z-index: 9;
@include media-breakpoint-up(md) {
height: 70px;
}
@include media-breakpoint-up(lg) {
height: 80px;
}
img {
height: 32px;
height: 20px;
object-fit: contain;
object-position: left;
transition: height 0.2s;
vertical-align: middle;
width: 100px;
@include media-breakpoint-up(md) {
height: 32px;
}
@include media-breakpoint-up(lg) {
width: 175px;
}
@ -113,6 +141,14 @@
padding-left: 0;
position: relative;
@include font-size(1.7rem);
@include media-breakpoint-down(md) {
display: none;
}
.fixed & {
display: block;
}
}
.mainNavigation {
@ -145,7 +181,7 @@
padding: divide($container-padding-x, 2);
}
&.fixed {
.fixed & {
display: inline-flex;
@include media-breakpoint-down(lg) {
@ -181,13 +217,9 @@
display: inline-flex;
float: right;
padding-right: 0;
padding-left: 0;
padding-left: divide($container-padding-x, 2);
width: 2.2rem;
@include media-breakpoint-up(sm) {
padding-left: divide($container-padding-x, 2);
}
@include media-breakpoint-up(md) {
display: none;
}
@ -235,7 +267,7 @@
top: 0;
}
&.fixed {
.fixed & {
> div {
opacity: 0;
transition: opacity 0s;
@ -276,6 +308,10 @@
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
@include media-breakpoint-down(md) {
display: none;
}
}
.headerSearch {
@ -305,6 +341,7 @@
.articleControls {
display: flex;
justify-content: flex-end;
left: 0;
position: absolute;
top: 50%;

View File

@ -3,12 +3,14 @@ import Private from './Private'
import Notifications from './Notifications'
import { Icon } from './Icon'
import { Modal } from './Modal'
import { Popup } from './Popup'
import AuthModal from './AuthModal'
import { t } from '../../utils/intl'
import { useModalStore, showModal, useWarningsStore } from '../../stores/ui'
import {useModalStore, showModal, useWarningsStore, toggleModal} from '../../stores/ui'
import { useAuthStore } from '../../stores/auth'
import { handleClientRouteLinkClick, router, Routes, useRouter } from '../../stores/router'
import styles from './Header.module.scss'
import stylesPopup from './Popup.module.scss'
import privateStyles from './Private.module.scss'
import { getPagePath } from '@nanostores/router'
import { clsx } from 'clsx'
@ -46,11 +48,10 @@ export const Header = (props: Props) => {
const toggleFixed = () => setFixed(!fixed())
// effects
createEffect(() => {
if (fixed() || getModal()) {
document.body.classList.add('fixed')
} else {
document.body.classList.remove('fixed')
}
const isFixed = fixed() || (getModal() && getModal() !== 'share');
document.body.classList.toggle('fixed', isFixed);
document.body.classList.toggle(styles.fixed, isFixed);
}, [fixed(), getModal()])
// derived
@ -68,8 +69,6 @@ export const Header = (props: Props) => {
onMount(() => {
let scrollTop = window.scrollY
// window.console.log(props.title)
const handleScroll = () => {
setIsScrollingBottom(window.scrollY > scrollTop)
setIsScrolled(window.scrollY > 0)
@ -95,7 +94,43 @@ export const Header = (props: Props) => {
<Modal name="auth">
<AuthModal />
</Modal>
<div class="wide-container">
<div class={clsx(styles.mainHeaderInner, 'wide-container')}>
<Popup name="share" class={clsx(styles.popupShare, stylesPopup.popupShare)}>
<ul class="nodash">
<li>
<a href="#">
<Icon name="vk-white" class={stylesPopup.icon}/>
VK
</a>
</li>
<li>
<a href="#">
<Icon name="facebook-white" class={stylesPopup.icon}/>
Facebook
</a>
</li>
<li>
<a href="#">
<Icon name="twitter-white" class={stylesPopup.icon}/>
Twitter
</a>
</li>
<li>
<a href="#">
<Icon name="telegram-white" class={stylesPopup.icon}/>
Telegram
</a>
</li>
<li>
<a href="#">
<Icon name="link-white" class={stylesPopup.icon}/>
{t('Copy link')}
</a>
</li>
</ul>
</Popup>
<nav class={clsx(styles.headerInner, 'row')} classList={{ fixed: fixed() }}>
<div class={clsx(styles.mainLogo, 'col-auto')}>
<a href={getPagePath(router, 'home')} onClick={handleClientRouteLinkClick}>
@ -113,7 +148,7 @@ export const Header = (props: Props) => {
>
<For each={resources}>
{(r) => (
<li classList={{ selected: r.route === getPage().route }}>
<li classList={{ [styles.selected]: r.route === getPage().route }}>
<a href={getPagePath(router, r.route, null)} onClick={handleClientRouteLinkClick}>
{r.name}
</a>
@ -159,7 +194,9 @@ export const Header = (props: Props) => {
</div>
<Show when={props.title}>
<div class={styles.articleControls}>
<Icon name="share-outline" class={styles.icon} />
<button onClick={() => {toggleModal('share')}}>
<Icon name="share-outline" class={styles.icon}/>
</button>
<a href="#comments">
<Icon name="comments-outline" class={styles.icon} />
</a>

View 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;
}

View 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>
)
}

View File

@ -18,11 +18,6 @@ export default () => {
<Icon name="pencil" />
</a>
</div>
<div class={clsx(styles.userControlItem, styles.userControlItemSearch)}>
<a href="/search">
<Icon name="search" />
</a>
</div>
<div class={clsx(styles.userControlItem, styles.userControlItemInbox)}>
<a href="/inbox">
{/*FIXME: replace with route*/}

View File

@ -3,7 +3,7 @@ import { AllAuthorsView } from '../Views/AllAuthors'
import type { PageProps } from '../types'
import { createSignal, onMount, Show } from 'solid-js'
import { loadAllAuthors } from '../../stores/zine/authors'
import { t } from '../../utils/intl'
import { Loading } from '../Loading'
export const AllAuthorsPage = (props: PageProps) => {
const [isLoaded, setIsLoaded] = createSignal<boolean>(Boolean(props.allAuthors))
@ -19,7 +19,7 @@ export const AllAuthorsPage = (props: PageProps) => {
return (
<MainLayout>
<Show when={isLoaded()} fallback={t('Loading')}>
<Show when={isLoaded()} fallback={<Loading />}>
<AllAuthorsView authors={props.allAuthors} />
</Show>
</MainLayout>

View File

@ -2,8 +2,8 @@ import { MainLayout } from '../Layouts/MainLayout'
import { AllTopicsView } from '../Views/AllTopics'
import type { PageProps } from '../types'
import { createSignal, onMount, Show } from 'solid-js'
import { t } from '../../utils/intl'
import { loadAllTopics } from '../../stores/zine/topics'
import { Loading } from '../Loading'
export const AllTopicsPage = (props: PageProps) => {
const [isLoaded, setIsLoaded] = createSignal<boolean>(Boolean(props.allTopics))
@ -19,7 +19,7 @@ export const AllTopicsPage = (props: PageProps) => {
return (
<MainLayout>
<Show when={isLoaded()} fallback={t('Loading')}>
<Show when={isLoaded()} fallback={<Loading />}>
<AllTopicsView topics={props.allTopics} />
</Show>
</MainLayout>

View File

@ -3,9 +3,9 @@ import { ArticleView } from '../Views/Article'
import type { PageProps } from '../types'
import { loadArticle, useArticlesStore } from '../../stores/zine/articles'
import { createMemo, onMount, Show } from 'solid-js'
import { t } from '../../utils/intl'
import type { Shout } from '../../graphql/types.gen'
import { useRouter } from '../../stores/router'
import { Loading } from '../Loading'
export const ArticlePage = (props: PageProps) => {
const sortedArticles = props.article ? [props.article] : []
@ -38,7 +38,7 @@ export const ArticlePage = (props: PageProps) => {
return (
<MainLayout headerTitle={article()?.title || ''}>
<Show when={Boolean(article())} fallback={t('Loading')}>
<Show when={Boolean(article())} fallback={<Loading />}>
<ArticleView article={article()} />
</Show>
</MainLayout>

View File

@ -4,8 +4,8 @@ import type { PageProps } from '../types'
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
import { loadArticlesForAuthors, resetSortedArticles } from '../../stores/zine/articles'
import { useRouter } from '../../stores/router'
import { t } from '../../utils/intl'
import { loadAuthor } from '../../stores/zine/authors'
import { Loading } from '../Loading'
export const AuthorPage = (props: PageProps) => {
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.authorArticles) && Boolean(props.author))
@ -37,7 +37,7 @@ export const AuthorPage = (props: PageProps) => {
return (
<MainLayout>
<Show when={isLoaded()} fallback={t('Loading')}>
<Show when={isLoaded()} fallback={<Loading />}>
<AuthorView author={props.author} authorArticles={props.authorArticles} authorSlug={slug()} />
</Show>
</MainLayout>

View File

@ -3,7 +3,7 @@ import { FeedView } from '../Views/Feed'
import type { PageProps } from '../types'
import { createSignal, onCleanup, onMount, Show } from 'solid-js'
import { loadRecentArticles, resetSortedArticles } from '../../stores/zine/articles'
import { t } from '../../utils/intl'
import { Loading } from '../Loading'
export const FeedPage = (props: PageProps) => {
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.feedArticles))
@ -22,7 +22,7 @@ export const FeedPage = (props: PageProps) => {
return (
<MainLayout>
<Show when={isLoaded()} fallback={t('Loading')}>
<Show when={isLoaded()} fallback={<Loading />}>
<FeedView articles={props.feedArticles} />
</Show>
</MainLayout>

View File

@ -2,9 +2,9 @@ import { HomeView } from '../Views/Home'
import { MainLayout } from '../Layouts/MainLayout'
import type { PageProps } from '../types'
import { createSignal, onCleanup, onMount, Show } from 'solid-js'
import { t } from '../../utils/intl'
import { loadPublishedArticles, resetSortedArticles } from '../../stores/zine/articles'
import { loadRandomTopics } from '../../stores/zine/topics'
import { Loading } from '../Loading'
export const HomePage = (props: PageProps) => {
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.homeArticles) && Boolean(props.randomTopics))
@ -24,7 +24,7 @@ export const HomePage = (props: PageProps) => {
return (
<MainLayout>
<Show when={isLoaded()} fallback={t('Loading')}>
<Show when={isLoaded()} fallback={<Loading />}>
<HomeView randomTopics={props.randomTopics} recentPublishedArticles={props.homeArticles || []} />
</Show>
</MainLayout>

View File

@ -3,8 +3,8 @@ import { SearchView } from '../Views/Search'
import type { PageProps } from '../types'
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
import { loadSearchResults, resetSortedArticles } from '../../stores/zine/articles'
import { t } from '../../utils/intl'
import { useRouter } from '../../stores/router'
import { Loading } from '../Loading'
export const SearchPage = (props: PageProps) => {
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.searchResults))
@ -34,7 +34,7 @@ export const SearchPage = (props: PageProps) => {
return (
<MainLayout>
<Show when={isLoaded()} fallback={t('Loading')}>
<Show when={isLoaded()} fallback={<Loading />}>
<SearchView results={props.searchResults || []} query={props.searchQuery} />
</Show>
</MainLayout>

View File

@ -4,8 +4,8 @@ import type { PageProps } from '../types'
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
import { loadArticlesForTopics, resetSortedArticles } from '../../stores/zine/articles'
import { useRouter } from '../../stores/router'
import { t } from '../../utils/intl'
import { loadTopic } from '../../stores/zine/topics'
import { Loading } from '../Loading'
export const TopicPage = (props: PageProps) => {
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.authorArticles) && Boolean(props.author))
@ -37,7 +37,7 @@ export const TopicPage = (props: PageProps) => {
return (
<MainLayout>
<Show when={isLoaded()} fallback={t('Loading')}>
<Show when={isLoaded()} fallback={<Loading />}>
<TopicView topic={props.topic} topicArticles={props.topicArticles} topicSlug={slug()} />
</Show>
</MainLayout>

View File

@ -7,7 +7,7 @@
margin-left: 0;
}
.topic-details__item {
.topicDetailsItem {
margin-bottom: 1.2rem;
@include media-breakpoint-up(md) {
@ -17,14 +17,28 @@
}
}
.topic-title {
.topicInRow {
align-items: center;
display: flex;
justify-content: space-between;
button {
margin-top: 0;
}
.topicTitle {
margin-bottom: 0;
}
}
.topicTitle {
font-weight: bold;
@include font-size(1.7rem);
margin-bottom: 0.8rem;
}
.topic__avatar {
.topicAvatar {
border-radius: 100%;
height: 64px;
margin-right: 1.2rem;
@ -41,7 +55,7 @@
}
}
.topic-description {
.topicDescription {
@include font-size(1.5rem);
color: #696969;
@ -52,14 +66,14 @@
}
}
.topic-description--short {
display: box;
.topicDescriptionShort {
display: block;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.topic-details {
.topicDetails {
@include font-size(1.7rem);
color: #9fa1a7;
@ -70,7 +84,7 @@
}
}
.topic-details__item {
.topicDetailsItem {
margin-right: 1.6rem;
white-space: nowrap;

View File

@ -1,6 +1,6 @@
import { capitalize, plural } from '../../utils'
import { Show } from 'solid-js/web'
import './Card.scss'
import style from './Card.module.scss'
import { createMemo } from 'solid-js'
import type { Topic } from '../../graphql/types.gen'
import { FollowingEntity } from '../../graphql/types.gen'
@ -15,6 +15,9 @@ interface TopicProps {
subscribed?: boolean
shortDescription?: boolean
subscribeButtonBottom?: boolean
additionalClass?: string
isTopicInRow?: boolean
iconButton?: boolean
}
export const TopicCard = (props: TopicProps) => {
@ -37,15 +40,21 @@ export const TopicCard = (props: TopicProps) => {
}
}
return (
<div class="topic" classList={{ row: !props.compact && !props.subscribeButtonBottom }}>
<div
class={style.topic}
classList={{
row: !props.compact && !props.subscribeButtonBottom,
[style.topicInRow]: props.isTopicInRow
}}
>
<div classList={{ 'col-md-7': !props.compact && !props.subscribeButtonBottom }}>
<Show when={props.topic.title}>
<div class="topic-title">
<div class={style.topicTitle}>
<a href={`/topic/${props.topic.slug}`}>{capitalize(props.topic.title || '')}</a>
</div>
</Show>
<Show when={props.topic.pic}>
<div class="topic__avatar">
<div class={style.topicAvatar}>
<a href={props.topic.slug}>
<img src={props.topic.pic} alt={props.topic.title} />
</a>
@ -53,15 +62,18 @@ export const TopicCard = (props: TopicProps) => {
</Show>
<Show when={!props.compact && props.topic?.body}>
<div class="topic-description" classList={{ 'topic-description--short': props.shortDescription }}>
<div
class={style.topicDescription}
classList={{ 'topic-description--short': props.shortDescription }}
>
{props.topic.body}
</div>
</Show>
<Show when={props.topic?.stat}>
<div class="topic-details">
<div class={style.topicDetails}>
<Show when={!props.compact}>
<span class="topic-details__item" classList={{ compact: props.compact }}>
<span class={style.topicDetailsTtem} classList={{ compact: props.compact }}>
{props.topic.stat?.shouts +
' ' +
t('post') +
@ -70,7 +82,7 @@ export const TopicCard = (props: TopicProps) => {
locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']
)}
</span>
<span class="topic-details__item" classList={{ compact: props.compact }}>
<span class={style.topicDetailsTtem} classList={{ compact: props.compact }}>
{props.topic.stat?.authors +
' ' +
t('author') +
@ -79,7 +91,7 @@ export const TopicCard = (props: TopicProps) => {
locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']
)}
</span>
<span class="topic-details__item" classList={{ compact: props.compact }}>
<span class={style.topicDetailsItem} classList={{ compact: props.compact }}>
{props.topic.stat?.followers +
' ' +
t('follower') +
@ -118,12 +130,16 @@ export const TopicCard = (props: TopicProps) => {
when={subscribed()}
fallback={
<button onClick={() => subscribe(true)} class="button--light">
+&nbsp;{t('Follow')}
<Show when={props.iconButton}>{/*<Icon name={}/>*/}</Show>
<Show when={!props.iconButton}>+&nbsp;{t('Follow')}</Show>
</button>
}
>
<button onClick={() => subscribe(false)} class="button--light">
-&nbsp;{t('Unfollow')}
<Show when={props.iconButton}>{/*<Icon name={}/>*/}</Show>
<Show when={!props.iconButton}>-&nbsp;{t('Unfollow')}</Show>
</button>
</Show>
</div>

View File

@ -31,6 +31,11 @@ export const AllAuthorsView = (props: Props) => {
const byLetter = createMemo<{ [letter: string]: Author[] }>(() => {
return sortedAuthors().reduce((acc, author) => {
if (!author.name) {
// name === null for new users
return acc
}
const letter = author.name[0].toUpperCase()
if (!acc[letter]) {
acc[letter] = []

View File

@ -9,8 +9,9 @@ import { useAuthorsStore } from '../../stores/zine/authors'
import { useArticlesStore } from '../../stores/zine/articles'
import '../../styles/Topic.scss'
// import { useTopicsStore } from '../../stores/zine/topics'
import { useTopicsStore } from '../../stores/zine/topics'
import { useRouter } from '../../stores/router'
import Beside from '../Feed/Beside'
// TODO: load reactions on client
type AuthorProps = {
@ -30,6 +31,7 @@ export const AuthorView = (props: AuthorProps) => {
sortedArticles: props.authorArticles
})
const { authorEntities } = useAuthorsStore({ authors: [props.author] })
const { topicsByAuthor } = useTopicsStore()
const author = createMemo(() => authorEntities()[props.authorSlug])
const { getSearchParams, changeSearchParam } = useRouter<AuthorPageSearchParams>()
@ -54,21 +56,22 @@ export const AuthorView = (props: AuthorProps) => {
{t('Recent')}
</button>
</li>
<li classList={{ selected: getSearchParams().by === 'rating' }}>
<button type="button" onClick={() => changeSearchParam('by', 'rating')}>
{t('Popular')}
</button>
</li>
<li classList={{ selected: getSearchParams().by === 'viewed' }}>
<button type="button" onClick={() => changeSearchParam('by', 'viewed')}>
{t('Views')}
</button>
</li>
<li classList={{ selected: getSearchParams().by === 'commented' }}>
<button type="button" onClick={() => changeSearchParam('by', 'commented')}>
{t('Discussing')}
</button>
</li>
{/*TODO: server sort*/}
{/*<li classList={{ selected: getSearchParams().by === 'rating' }}>*/}
{/* <button type="button" onClick={() => changeSearchParam('by', 'rating')}>*/}
{/* {t('Popular')}*/}
{/* </button>*/}
{/*</li>*/}
{/*<li classList={{ selected: getSearchParams().by === 'viewed' }}>*/}
{/* <button type="button" onClick={() => changeSearchParam('by', 'viewed')}>*/}
{/* {t('Views')}*/}
{/* </button>*/}
{/*</li>*/}
{/*<li classList={{ selected: getSearchParams().by === 'commented' }}>*/}
{/* <button type="button" onClick={() => changeSearchParam('by', 'commented')}>*/}
{/* {t('Discussing')}*/}
{/* </button>*/}
{/*</li>*/}
</ul>
</div>
<div class="col-md-4">
@ -79,24 +82,33 @@ export const AuthorView = (props: AuthorProps) => {
</div>
</div>
<div class="floor">
<h3 class="col-12">{title()}</h3>
<div class="row">
<Show when={sortedArticles().length > 0}>
{/*FIXME*/}
{/*<Beside*/}
{/* title={t('Topics which supported by author')}*/}
{/* values={getTopicsByAuthor()[author().slug].slice(0, 5)}*/}
{/* beside={articles()[0]}*/}
{/* wrapper={'topic'}*/}
{/* topicShortDescription={true}*/}
{/*/>*/}
<Row3 articles={sortedArticles().slice(1, 4)} />
<h3 class="col-12">{title()}</h3>
<div class="row">
<Show when={sortedArticles().length > 0}>
<Beside
title={t('Topics which supported by author')}
values={topicsByAuthor()[author().slug].slice(0, 5)}
beside={sortedArticles()[0]}
wrapper={'topic'}
topicShortDescription={true}
isTopicCompact={true}
isTopicInRow={true}
iconButton={true}
/>
<Row3 articles={sortedArticles().slice(1, 4)} />
<Show when={sortedArticles().length > 4}>
<Row2 articles={sortedArticles().slice(4, 6)} />
</Show>
<Show when={sortedArticles().length > 6}>
<Row3 articles={sortedArticles().slice(6, 9)} />
</Show>
<Show when={sortedArticles().length > 9}>
<Row3 articles={sortedArticles().slice(9, 12)} />
</Show>
</div>
</Show>
</div>
</Show>
</div>

View File

@ -32,14 +32,6 @@ export const TopicView = (props: TopicProps) => {
const topic = createMemo(() => topicEntities()[props.topicSlug])
/*
const slug = createMemo<string>(() => {
let slug = props?.slug
if (props?.slug.startsWith('@')) slug = slug.replace('@', '')
return slug
})
*/
const title = createMemo(() => {
const m = getSearchParams().by
if (m === 'viewed') return t('Top viewed')
@ -60,21 +52,22 @@ export const TopicView = (props: TopicProps) => {
{t('Recent')}
</button>
</li>
<li classList={{ selected: getSearchParams().by === 'rating' }}>
<button type="button" onClick={() => changeSearchParam('by', 'rating')}>
{t('Popular')}
</button>
</li>
<li classList={{ selected: getSearchParams().by === 'viewed' }}>
<button type="button" onClick={() => changeSearchParam('by', 'viewed')}>
{t('Views')}
</button>
</li>
<li classList={{ selected: getSearchParams().by === 'commented' }}>
<button type="button" onClick={() => changeSearchParam('by', 'commented')}>
{t('Discussing')}
</button>
</li>
{/*TODO: server sort*/}
{/*<li classList={{ selected: getSearchParams().by === 'rating' }}>*/}
{/* <button type="button" onClick={() => changeSearchParam('by', 'rating')}>*/}
{/* {t('Popular')}*/}
{/* </button>*/}
{/*</li>*/}
{/*<li classList={{ selected: getSearchParams().by === 'viewed' }}>*/}
{/* <button type="button" onClick={() => changeSearchParam('by', 'viewed')}>*/}
{/* {t('Views')}*/}
{/* </button>*/}
{/*</li>*/}
{/*<li classList={{ selected: getSearchParams().by === 'commented' }}>*/}
{/* <button type="button" onClick={() => changeSearchParam('by', 'commented')}>*/}
{/* {t('Discussing')}*/}
{/* </button>*/}
{/*</li>*/}
</ul>
</div>
<div class="col-md-4">
@ -92,7 +85,10 @@ export const TopicView = (props: TopicProps) => {
<For each={sortedArticles().slice(0, 6)}>
{(article) => (
<div class="col-md-6">
<ArticleCard article={article} />
<ArticleCard
article={article}
settings={{ isFloorImportant: true, isBigTitle: true }}
/>
</div>
)}
</For>
@ -108,11 +104,11 @@ export const TopicView = (props: TopicProps) => {
beside={sortedArticles()[6]}
wrapper={'author'}
/>
<Row3 articles={sortedArticles().slice(6, 9)} />
<Row2 articles={sortedArticles().slice(9, 11)} />
<Row3 articles={sortedArticles().slice(11, 14)} />
<Row3 articles={sortedArticles().slice(14, 17)} />
<Row3 articles={sortedArticles().slice(17, 20)} />
<Row3 articles={sortedArticles().slice(7, 10)} />
<Row2 articles={sortedArticles().slice(10, 12)} />
<Row3 articles={sortedArticles().slice(12, 15)} />
<Row3 articles={sortedArticles().slice(15, 18)} />
<Row3 articles={sortedArticles().slice(18, 21)} />
</Show>
</div>
</Show>

View File

@ -20,6 +20,7 @@
"Comments": "Комментарии",
"Communities": "Сообщества",
"Create account": "Создать аккаунт",
"Copy link": "Скопировать ссылку",
"Delete": "Удалить",
"Discours": "Дискурс",
"Discours is an intellectual environment, a web space and tools that allows authors to collaborate with readers and come together to co-create publications and media projects": "Дискурс — это интеллектуальная среда, веб-пространство и инструменты, которые позволяют авторам сотрудничать с читателями и объединяться для совместного создания публикаций и медиапроектов",

View File

@ -19,8 +19,9 @@ const modal = atom<ModalType>(null)
const warnings = atom<Warning[]>([])
export const showModal = (modalType: ModalType) => modal.set(modalType)
export const hideModal = () => modal.set(null)
export const toggleModal = (modalType) => modal.get() ? hideModal() : showModal(modalType)
export const clearWarns = () => warnings.set([])
export const warn = (warning: Warning) => warnings.set([...warnings.get(), warning])

View File

@ -38,6 +38,7 @@ body {
&.fixed {
overflow: hidden;
position: fixed;
width: 100%;
}
}
@ -210,7 +211,7 @@ button {
font-weight: 400;
margin-top: 0.6rem;
padding: 0.6rem 1.2rem 1rem 1rem;
padding: 0.6rem 1.2rem 0.6rem 1rem;
}
form {
@ -460,29 +461,48 @@ figcaption {
}
}
.floor--7 {
@include media-breakpoint-down(md) {
margin-bottom: 1em;
}
.col-md-6 {
margin-bottom: 1.6em;
@include media-breakpoint-down(md) {
margin-right: 0;
}
}
}
.floor--important {
background: #000;
color: #fff;
padding: $grid-gutter-width 0;
padding-bottom: $container-padding-x;
padding-top: $container-padding-x;
@include media-breakpoint-up(md) {
padding-bottom: $grid-gutter-width;
padding-top: $grid-gutter-width;
}
h2 {
@include font-size(4.4rem);
text-align: center;
}
@include media-breakpoint-down(md) {
margin-bottom: 5rem;
}
.all-materials,
.shout-card__title {
.all-materials {
a {
color: #fff;
}
}
.shout__topic,
.shout__author {
a {
color: rgb(255 255 255 / 50%);
}
}
a:hover {
background: #fff;
color: #000 !important;
@ -503,25 +523,6 @@ figcaption {
}
}
.shout__topic {
font-size: 1.2rem;
letter-spacing: 0.08em;
margin-bottom: 0.8rem;
text-transform: uppercase;
transition: background-color 0.2s;
a {
background: transparent;
border: none;
color: $link-color;
&:hover {
background: $link-color;
color: #fff !important;
}
}
}
/* stylelint-disable-next-line */
astro-island {
display: flex !important;
@ -533,7 +534,9 @@ astro-island {
.main-content {
flex: 1 100%;
min-height: 300px;
padding-top: 100px;
position: relative;
transition: all 1s ease;
}