merged
This commit is contained in:
commit
1054dee23d
25
__checks__/discoursio-webapp.check.js
Normal file
25
__checks__/discoursio-webapp.check.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
const { chromium } = require('playwright')
|
||||||
|
|
||||||
|
const checkUrl = async (page, targetUrl, pageName) => {
|
||||||
|
const response = await page.goto(targetUrl)
|
||||||
|
if (response.status() > 399) {
|
||||||
|
throw new Error(`Failed with response code ${response.status()}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.screenshot({ path: `${pageName}.jpg` })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const browser = await chromium.launch()
|
||||||
|
const page = await browser.newPage()
|
||||||
|
|
||||||
|
const targetUrl = process.env.ENVIRONMENT_URL || 'https://testing.discours.io'
|
||||||
|
|
||||||
|
await checkUrl(page, targetUrl, 'main')
|
||||||
|
await checkUrl(page, `${targetUrl}/authors`, 'authors')
|
||||||
|
await checkUrl(page, `${targetUrl}/topics`, 'topics')
|
||||||
|
await page.close()
|
||||||
|
await browser.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
await run()
|
|
@ -26,6 +26,7 @@
|
||||||
"server": "node server/server.mjs",
|
"server": "node server/server.mjs",
|
||||||
"start": "astro dev",
|
"start": "astro dev",
|
||||||
"start:local": "cross-env PUBLIC_API_URL=http://127.0.0.1:8080 astro dev",
|
"start:local": "cross-env PUBLIC_API_URL=http://127.0.0.1:8080 astro dev",
|
||||||
|
"start:staging": "cross-env PUBLIC_API_URL=https://testapi.discours.io astro dev",
|
||||||
"typecheck": "astro check && tsc --noEmit",
|
"typecheck": "astro check && tsc --noEmit",
|
||||||
"typecheck:watch": "tsc --noEmit --watch",
|
"typecheck:watch": "tsc --noEmit --watch",
|
||||||
"vercel-build": "astro build"
|
"vercel-build": "astro build"
|
||||||
|
|
|
@ -3,13 +3,17 @@ import './Full.scss'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import ArticleComment from './Comment'
|
import ArticleComment from './Comment'
|
||||||
import { AuthorCard } from '../Author/Card'
|
import { AuthorCard } from '../Author/Card'
|
||||||
import { createMemo, For, onMount, Show } from 'solid-js'
|
import { createMemo, createSignal, For, onMount, Show } from 'solid-js'
|
||||||
import type { Author, Reaction, Shout } from '../../graphql/types.gen'
|
import type { Author, Reaction, Shout } from '../../graphql/types.gen'
|
||||||
import { t } from '../../utils/intl'
|
import { t } from '../../utils/intl'
|
||||||
import { showModal } from '../../stores/ui'
|
import { showModal } from '../../stores/ui'
|
||||||
import MD from './MD'
|
import MD from './MD'
|
||||||
import { SharePopup } from './SharePopup'
|
import { SharePopup } from './SharePopup'
|
||||||
import { useSession } from '../../context/session'
|
import { useSession } from '../../context/session'
|
||||||
|
import stylesHeader from '../Nav/Header.module.scss'
|
||||||
|
import styles from '../../styles/Article.module.scss'
|
||||||
|
import RatingControl from './RatingControl'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
|
||||||
const MAX_COMMENT_LEVEL = 6
|
const MAX_COMMENT_LEVEL = 6
|
||||||
|
|
||||||
|
@ -39,6 +43,7 @@ const formatDate = (date: Date) => {
|
||||||
export const FullArticle = (props: ArticleProps) => {
|
export const FullArticle = (props: ArticleProps) => {
|
||||||
const { session } = useSession()
|
const { session } = useSession()
|
||||||
const formattedDate = createMemo(() => formatDate(new Date(props.article.createdAt)))
|
const formattedDate = createMemo(() => formatDate(new Date(props.article.createdAt)))
|
||||||
|
const [isSharePopupVisible, setIsSharePopupVisible] = createSignal(false)
|
||||||
|
|
||||||
const mainTopic = () =>
|
const mainTopic = () =>
|
||||||
(props.article.topics?.find((topic) => topic?.slug === props.article.mainTopic)?.title || '').replace(
|
(props.article.topics?.find((topic) => topic?.slug === props.article.mainTopic)?.title || '').replace(
|
||||||
|
@ -64,8 +69,8 @@ export const FullArticle = (props: ArticleProps) => {
|
||||||
return (
|
return (
|
||||||
<div class="shout wide-container">
|
<div class="shout wide-container">
|
||||||
<article class="col-md-6 shift-content">
|
<article class="col-md-6 shift-content">
|
||||||
<div class="shout__header">
|
<div class={styles.shoutHeader}>
|
||||||
<div class="shout__topic">
|
<div class={styles.shoutTopic}>
|
||||||
<a href={`/topic/${props.article.mainTopic}`} innerHTML={mainTopic() || ''} />
|
<a href={`/topic/${props.article.mainTopic}`} innerHTML={mainTopic() || ''} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -74,7 +79,7 @@ export const FullArticle = (props: ArticleProps) => {
|
||||||
<h4>{capitalize(props.article.subtitle, false)}</h4>
|
<h4>{capitalize(props.article.subtitle, false)}</h4>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<div class="shout__author">
|
<div class={styles.shoutAuthor}>
|
||||||
<For each={props.article.authors}>
|
<For each={props.article.authors}>
|
||||||
{(a: Author, index) => (
|
{(a: Author, index) => (
|
||||||
<>
|
<>
|
||||||
|
@ -84,11 +89,11 @@ export const FullArticle = (props: ArticleProps) => {
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
<div class="shout__cover" style={{ 'background-image': `url('${props.article.cover}')` }} />
|
<div class={styles.shoutCover} style={{ 'background-image': `url('${props.article.cover}')` }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={Boolean(props.article.body)}>
|
<Show when={Boolean(props.article.body)}>
|
||||||
<div class="shout__body">
|
<div class={styles.shoutBody}>
|
||||||
<Show
|
<Show
|
||||||
when={!props.article.body.startsWith('<')}
|
when={!props.article.body.startsWith('<')}
|
||||||
fallback={<div innerHTML={props.article.body} />}
|
fallback={<div innerHTML={props.article.body} />}
|
||||||
|
@ -100,63 +105,82 @@ export const FullArticle = (props: ArticleProps) => {
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<div class="col-md-8 shift-content">
|
<div class="col-md-8 shift-content">
|
||||||
<div class="shout-stats">
|
<div class={styles.shoutStats}>
|
||||||
<div class="shout-stats__item shout-stats__item--likes">
|
<div class={styles.shoutStatsItem}>
|
||||||
<Icon name="like" />
|
<RatingControl rating={props.article.stat?.rating} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={clsx(styles.shoutStatsItem, styles.shoutStatsItemLikes)}>
|
||||||
|
<Icon name="like" class={styles.icon} />
|
||||||
{props.article.stat?.rating || ''}
|
{props.article.stat?.rating || ''}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="shout-stats__item">
|
<div class={styles.shoutStatsItem}>
|
||||||
<Icon name="comment" />
|
<Icon name="comment" class={styles.icon} />
|
||||||
{props.article.stat?.commented || ''}
|
{props.article.stat?.commented || ''}
|
||||||
</div>
|
</div>
|
||||||
<div class="shout-stats__item">
|
|
||||||
<Icon name="view" />
|
|
||||||
{props.article.stat?.viewed}
|
|
||||||
</div>
|
|
||||||
{/*FIXME*/}
|
{/*FIXME*/}
|
||||||
{/*<div class="shout-stats__item">*/}
|
{/*<div class={styles.shoutStatsItem}>*/}
|
||||||
{/* <a href="#bookmark" onClick={() => console.log(props.article.slug, 'articles')}>*/}
|
{/* <a href="#bookmark" onClick={() => console.log(props.article.slug, 'articles')}>*/}
|
||||||
{/* <Icon name={'bookmark' + (bookmarked() ? '' : '-x')} />*/}
|
{/* <Icon name={'bookmark' + (bookmarked() ? '' : '-x')} />*/}
|
||||||
{/* </a>*/}
|
{/* </a>*/}
|
||||||
{/*</div>*/}
|
{/*</div>*/}
|
||||||
<div class="shout-stats__item">
|
<div class={styles.shoutStatsItem}>
|
||||||
<SharePopup
|
<SharePopup
|
||||||
trigger={
|
onVisibilityChange={(isVisible) => {
|
||||||
<a href="#" onClick={(event) => event.preventDefault()}>
|
setIsSharePopupVisible(isVisible)
|
||||||
<Icon name="share" />
|
}}
|
||||||
</a>
|
containerCssClass={stylesHeader.control}
|
||||||
}
|
trigger={<Icon name="share" class={styles.icon} />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class={styles.shoutStatsItem}>
|
||||||
|
<Icon name="bookmark" class={styles.icon} />
|
||||||
|
</div>
|
||||||
|
|
||||||
{/*FIXME*/}
|
{/*FIXME*/}
|
||||||
{/*<Show when={canEdit()}>*/}
|
{/*<Show when={canEdit()}>*/}
|
||||||
{/* <div class="shout-stats__item">*/}
|
{/* <div class={styles.shoutStatsItem}>*/}
|
||||||
{/* <a href="/edit">*/}
|
{/* <a href="/edit">*/}
|
||||||
{/* <Icon name="edit" />*/}
|
{/* <Icon name="edit" />*/}
|
||||||
{/* {t('Edit')}*/}
|
{/* {t('Edit')}*/}
|
||||||
{/* </a>*/}
|
{/* </a>*/}
|
||||||
{/* </div>*/}
|
{/* </div>*/}
|
||||||
{/*</Show>*/}
|
{/*</Show>*/}
|
||||||
<div class="shout-stats__item shout-stats__item--date">{formattedDate}</div>
|
<div class={clsx(styles.shoutStatsItem, styles.shoutStatsItemAdditionalData)}>
|
||||||
|
<div class={clsx(styles.shoutStatsItem, styles.shoutStatsItemAdditionalDataItem)}>
|
||||||
|
{formattedDate}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Show when={props.article.stat?.viewed}>
|
||||||
|
<div class={clsx(styles.shoutStatsItem, styles.shoutStatsItemAdditionalDataItem)}>
|
||||||
|
<Icon name="view" class={styles.icon} />
|
||||||
|
{props.article.stat?.viewed}
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="topics-list">
|
<div class={styles.topicsList}>
|
||||||
<For each={props.article.topics}>
|
<For each={props.article.topics}>
|
||||||
{(topic) => (
|
{(topic) => (
|
||||||
<div class="shout__topic">
|
<div class={styles.shoutTopic}>
|
||||||
<a href={`/topic/${topic.slug}`}>{topic.title}</a>
|
<a href={`/topic/${topic.slug}`}>{topic.title}</a>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="shout__authors-list">
|
<div class={styles.shoutAuthorsList}>
|
||||||
<Show when={props.article?.authors?.length > 1}>
|
<Show when={props.article?.authors?.length > 1}>
|
||||||
<h4>{t('Authors')}</h4>
|
<h4>{t('Authors')}</h4>
|
||||||
</Show>
|
</Show>
|
||||||
<For each={props.article?.authors}>
|
<For each={props.article?.authors}>
|
||||||
{(a: Author) => <AuthorCard author={a} compact={false} hasLink={true} />}
|
{(a: Author) => (
|
||||||
|
<div class="col-md-6">
|
||||||
|
<AuthorCard author={a} compact={false} hasLink={true} liteButtons={true} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -176,7 +200,7 @@ export const FullArticle = (props: ArticleProps) => {
|
||||||
</For>
|
</For>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={!session()?.user?.slug}>
|
<Show when={!session()?.user?.slug}>
|
||||||
<div class="comment-warning" id="comments">
|
<div class={styles.commentWarning} id="comments">
|
||||||
{t('To leave a comment you please')}
|
{t('To leave a comment you please')}
|
||||||
<a
|
<a
|
||||||
href={''}
|
href={''}
|
||||||
|
@ -190,7 +214,7 @@ export const FullArticle = (props: ArticleProps) => {
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={session()?.user?.slug}>
|
<Show when={session()?.user?.slug}>
|
||||||
<textarea class="write-comment" rows="1" placeholder={t('Write comment')} />
|
<textarea class={styles.writeComment} rows="1" placeholder={t('Write comment')} />
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
29
src/components/Article/RatingControl.module.scss
Normal file
29
src/components/Article/RatingControl.module.scss
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
.rating {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratingValue {
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratingControl {
|
||||||
|
align-items: center;
|
||||||
|
border: 2px solid;
|
||||||
|
border-radius: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
height: 0.9em;
|
||||||
|
line-height: 0;
|
||||||
|
@include font-size(3.6rem);
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
width: 0.9em;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #000;
|
||||||
|
border-color: #000;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
19
src/components/Article/RatingControl.tsx
Normal file
19
src/components/Article/RatingControl.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import styles from './RatingControl.module.scss'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
|
||||||
|
interface RatingControlProps {
|
||||||
|
rating?: number
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RatingControl = (props: RatingControlProps) => {
|
||||||
|
return (
|
||||||
|
<div class={clsx(props.class, styles.rating)}>
|
||||||
|
<button class={styles.ratingControl}>−</button>
|
||||||
|
<span class={styles.ratingValue}>{props?.rating || ''}</span>
|
||||||
|
<button class={styles.ratingControl}>+</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RatingControl
|
|
@ -166,6 +166,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
|
display: inline-block;
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,6 +207,7 @@
|
||||||
background-color: #000;
|
background-color: #000;
|
||||||
border-color: #000;
|
border-color: #000;
|
||||||
border-radius: 0.8rem;
|
border-radius: 0.8rem;
|
||||||
|
color: #fff;
|
||||||
float: none;
|
float: none;
|
||||||
padding-bottom: 0.6rem;
|
padding-bottom: 0.6rem;
|
||||||
padding-top: 0.6rem;
|
padding-top: 0.6rem;
|
||||||
|
|
|
@ -9,7 +9,6 @@ import { locale } from '../../stores/ui'
|
||||||
import { follow, unfollow } from '../../stores/zine/common'
|
import { follow, unfollow } from '../../stores/zine/common'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { useSession } from '../../context/session'
|
import { useSession } from '../../context/session'
|
||||||
import { StatMetrics } from '../_shared/StatMetrics'
|
|
||||||
|
|
||||||
interface AuthorCardProps {
|
interface AuthorCardProps {
|
||||||
caption?: string
|
caption?: string
|
||||||
|
@ -23,6 +22,7 @@ interface AuthorCardProps {
|
||||||
noSocialButtons?: boolean
|
noSocialButtons?: boolean
|
||||||
isAuthorsList?: boolean
|
isAuthorsList?: boolean
|
||||||
truncateBio?: boolean
|
truncateBio?: boolean
|
||||||
|
liteButtons?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AuthorCard = (props: AuthorCardProps) => {
|
export const AuthorCard = (props: AuthorCardProps) => {
|
||||||
|
@ -117,9 +117,17 @@ export const AuthorCard = (props: AuthorCardProps) => {
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={!props.compact && !props.isAuthorsList}>
|
<Show when={!props.compact && !props.isAuthorsList}>
|
||||||
<button class={clsx(styles.buttonWrite, styles.button, 'button button--subscribe-topic')}>
|
<button
|
||||||
<Icon name="edit" class={styles.icon} />
|
class={styles.button}
|
||||||
{t('Write')}
|
classList={{
|
||||||
|
[styles.buttonSubscribe]: !props.isAuthorsList,
|
||||||
|
'button--subscribe': !props.isAuthorsList,
|
||||||
|
'button--subscribe-topic': props.isAuthorsList,
|
||||||
|
[styles.buttonWrite]: props.liteButtons && props.isAuthorsList
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon name="comment" class={styles.icon} />
|
||||||
|
<Show when={!props.liteButtons}>{t('Write')}</Show>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Show when={!props.noSocialButtons}>
|
<Show when={!props.noSocialButtons}>
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
.user-details {
|
.user-details {
|
||||||
margin-bottom: 4.4rem;
|
margin-bottom: 5.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.author-page {
|
.author-page {
|
||||||
.view-switcher {
|
.view-switcher {
|
||||||
@include font-size(1.5rem);
|
@include font-size(1.5rem);
|
||||||
|
|
||||||
|
margin-top: 0;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -460,36 +460,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.rating {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ratingValue {
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 0 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ratingControl {
|
|
||||||
align-items: center;
|
|
||||||
border: 2px solid;
|
|
||||||
border-radius: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
height: 0.9em;
|
|
||||||
line-height: 0;
|
|
||||||
@include font-size(3.6rem);
|
|
||||||
|
|
||||||
padding: 0;
|
|
||||||
width: 0.9em;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #000;
|
|
||||||
border-color: #000;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shoutCardVertical {
|
.shoutCardVertical {
|
||||||
aspect-ratio: auto;
|
aspect-ratio: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -8,6 +8,7 @@ import styles from './Card.module.scss'
|
||||||
import { locale } from '../../stores/ui'
|
import { locale } from '../../stores/ui'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import CardTopic from './CardTopic'
|
import CardTopic from './CardTopic'
|
||||||
|
import RatingControl from '../Article/RatingControl'
|
||||||
|
|
||||||
interface ArticleCardProps {
|
interface ArticleCardProps {
|
||||||
settings?: {
|
settings?: {
|
||||||
|
@ -158,11 +159,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
<Show when={props.settings?.isFeedMode}>
|
<Show when={props.settings?.isFeedMode}>
|
||||||
<section class={styles.shoutCardDetails}>
|
<section class={styles.shoutCardDetails}>
|
||||||
<div class={styles.shoutCardDetailsContent}>
|
<div class={styles.shoutCardDetailsContent}>
|
||||||
<div class={clsx(styles.shoutCardDetailsItem, styles.rating)}>
|
<RatingControl rating={stat?.rating} class={styles.shoutCardDetailsItem} />
|
||||||
<button class={styles.ratingControl}>−</button>
|
|
||||||
<span class={styles.ratingValue}>{stat?.rating || ''}</span>
|
|
||||||
<button class={styles.ratingControl}>+</button>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class={clsx(
|
class={clsx(
|
||||||
styles.shoutCardDetailsItem,
|
styles.shoutCardDetailsItem,
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
border: 3px solid #fff;
|
border: 3px solid #fff;
|
||||||
}
|
}
|
||||||
> .imageHolder {
|
.imageHolder {
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -26,12 +26,12 @@
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .letter {
|
.letter {
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .letter {
|
.letter {
|
||||||
margin-bottom: -2px;
|
margin-bottom: -2px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
|
|
||||||
> .letter {
|
.letter {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,10 @@ const DialogAvatar = (props: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.DialogAvatar, props.online && styles.online, `${styles[props.size]}`)}
|
class={clsx(styles.DialogAvatar, {
|
||||||
|
[styles.online]: props.online,
|
||||||
|
[styles.small]: props.size === 'small'
|
||||||
|
})}
|
||||||
style={{ 'background-color': `${randomBg()}` }}
|
style={{ 'background-color': `${randomBg()}` }}
|
||||||
>
|
>
|
||||||
<Show when={props.url} fallback={() => <div class={styles.letter}>{nameFirstLetter}</div>}>
|
<Show when={props.url} fallback={() => <div class={styles.letter}>{nameFirstLetter}</div>}>
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
height: 22px;
|
height: 22px;
|
||||||
line-height: 6px;
|
line-height: 6px;
|
||||||
|
|
||||||
> span {
|
span {
|
||||||
margin-bottom: -2px;
|
margin-bottom: -2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import styles from './DialogCard.module.scss'
|
import styles from './DialogCard.module.scss'
|
||||||
import DialogAvatar from './DialogAvatar'
|
import DialogAvatar from './DialogAvatar'
|
||||||
import type { Author, AuthResult } from '../../graphql/types.gen'
|
import type { Author } from '../../graphql/types.gen'
|
||||||
import { useSession } from '../../context/session'
|
|
||||||
import { createEffect, createMemo, createSignal } from 'solid-js'
|
|
||||||
import { apiClient } from '../../utils/apiClient'
|
import { apiClient } from '../../utils/apiClient'
|
||||||
|
|
||||||
type DialogProps = {
|
type DialogProps = {
|
||||||
|
|
|
@ -129,7 +129,7 @@ export const Header = (props: Props) => {
|
||||||
containerCssClass={styles.control}
|
containerCssClass={styles.control}
|
||||||
trigger={<Icon name="share-outline" class={styles.icon} />}
|
trigger={<Icon name="share-outline" class={styles.icon} />}
|
||||||
/>
|
/>
|
||||||
<a href={'/inbox'} class={styles.control}>
|
<a href={getPagePath(router, 'inbox')} class={styles.control}>
|
||||||
<Icon name="comments-outline" class={styles.icon} />
|
<Icon name="comments-outline" class={styles.icon} />
|
||||||
</a>
|
</a>
|
||||||
<a href="#" class={styles.control} onClick={(event) => event.preventDefault()}>
|
<a href="#" class={styles.control} onClick={(event) => event.preventDefault()}>
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { PageWrap } from '../_shared/PageWrap'
|
import { PageWrap } from '../_shared/PageWrap'
|
||||||
import { InboxView } from '../Views/Inbox'
|
import { InboxView } from '../Views/Inbox'
|
||||||
import type { PageProps } from '../types'
|
|
||||||
|
|
||||||
export const InboxPage = (props: PageProps) => {
|
export const InboxPage = () => {
|
||||||
return (
|
return (
|
||||||
<PageWrap hideFooter={true}>
|
<PageWrap hideFooter={true}>
|
||||||
<InboxView />
|
<InboxView />
|
||||||
|
|
|
@ -70,7 +70,7 @@ export const LayoutShoutsPage = (props: PageProps) => {
|
||||||
onCleanup(() => resetSortedArticles())
|
onCleanup(() => resetSortedArticles())
|
||||||
|
|
||||||
const ModeSwitcher = () => (
|
const ModeSwitcher = () => (
|
||||||
<div class="container">
|
<div class="wide-container">
|
||||||
<div class={clsx(styles.groupControls, 'row group__controls')}>
|
<div class={clsx(styles.groupControls, 'row group__controls')}>
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<ul class="view-switcher">
|
<ul class="view-switcher">
|
||||||
|
|
|
@ -4,8 +4,6 @@ import { t } from '../../utils/intl'
|
||||||
import type { Shout, Reaction } from '../../graphql/types.gen'
|
import type { Shout, Reaction } from '../../graphql/types.gen'
|
||||||
import { useReactionsStore } from '../../stores/zine/reactions'
|
import { useReactionsStore } from '../../stores/zine/reactions'
|
||||||
|
|
||||||
import '../../styles/Article.scss'
|
|
||||||
|
|
||||||
interface ArticlePageProps {
|
interface ArticlePageProps {
|
||||||
article: Shout
|
article: Shout
|
||||||
reactions?: Reaction[]
|
reactions?: Reaction[]
|
||||||
|
@ -32,16 +30,14 @@ export const ArticleView = (props: ArticlePageProps) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="article-page">
|
<Show fallback={<div class="center">{t('Loading')}</div>} when={props.article}>
|
||||||
<Show fallback={<div class="center">{t('Loading')}</div>} when={props.article}>
|
<Suspense>
|
||||||
<Suspense>
|
<FullArticle
|
||||||
<FullArticle
|
article={props.article}
|
||||||
article={props.article}
|
reactions={reactionsByShout()[props.article.slug]}
|
||||||
reactions={reactionsByShout()[props.article.slug]}
|
isCommentsLoading={getIsCommentsLoading()}
|
||||||
isCommentsLoading={getIsCommentsLoading()}
|
/>
|
||||||
/>
|
</Suspense>
|
||||||
</Suspense>
|
</Show>
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,8 +100,6 @@ export const AuthorView = (props: AuthorProps) => {
|
||||||
<span class="mode-switcher__control">{t('All posts')}</span>
|
<span class="mode-switcher__control">{t('All posts')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="col-12">{title()}</h3>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,19 @@
|
||||||
import { For, createSignal, Show, onMount, createEffect } from 'solid-js'
|
import { For, createSignal, Show, onMount, createEffect, createMemo } from 'solid-js'
|
||||||
import { PageWrap } from '../_shared/PageWrap'
|
import type { Author } from '../../graphql/types.gen'
|
||||||
import type { Author, Chat } from '../../graphql/types.gen'
|
|
||||||
import { AuthorCard } from '../Author/Card'
|
import { AuthorCard } from '../Author/Card'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { Loading } from '../Loading'
|
import { Loading } from '../Loading'
|
||||||
import DialogCard from '../Inbox/DialogCard'
|
import DialogCard from '../Inbox/DialogCard'
|
||||||
import Search from '../Inbox/Search'
|
import Search from '../Inbox/Search'
|
||||||
import { loadAllAuthors, useAuthorsStore } from '../../stores/zine/authors'
|
|
||||||
import MarkdownIt from 'markdown-it'
|
|
||||||
import { useSession } from '../../context/session'
|
import { useSession } from '../../context/session'
|
||||||
|
|
||||||
import '../../styles/Inbox.scss'
|
import '../../styles/Inbox.scss'
|
||||||
// Для моков
|
// Для моков
|
||||||
import { createClient } from '@urql/core'
|
import { createClient } from '@urql/core'
|
||||||
import Message from '../Inbox/Message'
|
import Message from '../Inbox/Message'
|
||||||
import { loadAuthorsBy, loadChats, chats, setChats } from '../../stores/inbox'
|
import { loadAuthorsBy, loadChats } from '../../stores/inbox'
|
||||||
|
import { t } from '../../utils/intl'
|
||||||
|
|
||||||
const md = new MarkdownIt({
|
|
||||||
linkify: true
|
|
||||||
})
|
|
||||||
const OWNER_ID = '501'
|
const OWNER_ID = '501'
|
||||||
const client = createClient({
|
const client = createClient({
|
||||||
url: 'https://graphqlzero.almansi.me/api'
|
url: 'https://graphqlzero.almansi.me/api'
|
||||||
|
@ -63,7 +58,7 @@ const postMessage = async (msg: string) => {
|
||||||
const handleGetChats = async () => {
|
const handleGetChats = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await loadChats()
|
const response = await loadChats()
|
||||||
setChats(response as unknown as Chat[])
|
console.log('!!! response:', response)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
}
|
}
|
||||||
|
@ -75,14 +70,10 @@ export const InboxView = () => {
|
||||||
const [cashedAuthors, setCashedAuthors] = createSignal<Author[]>([])
|
const [cashedAuthors, setCashedAuthors] = createSignal<Author[]>([])
|
||||||
const [postMessageText, setPostMessageText] = createSignal('')
|
const [postMessageText, setPostMessageText] = createSignal('')
|
||||||
const [loading, setLoading] = createSignal<boolean>(false)
|
const [loading, setLoading] = createSignal<boolean>(false)
|
||||||
const [currentSlug, setCurrentSlug] = createSignal<Author['slug'] | null>()
|
// const [currentSlug, setCurrentSlug] = createSignal<Author['slug'] | null>()
|
||||||
|
|
||||||
const { session } = useSession()
|
const { session } = useSession()
|
||||||
createEffect(() => {
|
const currentSlug = createMemo(() => session()?.user?.slug)
|
||||||
console.log('!!! session():', session())
|
|
||||||
setCurrentSlug(session()?.user?.slug)
|
|
||||||
console.log('!!! chats:', chats())
|
|
||||||
})
|
|
||||||
|
|
||||||
// Поиск по диалогам
|
// Поиск по диалогам
|
||||||
const getQuery = (query) => {
|
const getQuery = (query) => {
|
||||||
|
@ -90,7 +81,7 @@ export const InboxView = () => {
|
||||||
const match = userSearch(authors(), query())
|
const match = userSearch(authors(), query())
|
||||||
setAuthors(match)
|
setAuthors(match)
|
||||||
} else {
|
} else {
|
||||||
setAuthors(cashedAuthors)
|
setAuthors(cashedAuthors())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,10 +144,10 @@ export const InboxView = () => {
|
||||||
<div class="chat-list__types">
|
<div class="chat-list__types">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<strong>Все</strong>
|
<strong>{t('All')}</strong>
|
||||||
</li>
|
</li>
|
||||||
<li onClick={handleGetChats}>Переписки</li>
|
<li onClick={handleGetChats}>{t('Conversations')}</li>
|
||||||
<li>Группы</li>
|
<li>{t('Groups')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="holder">
|
<div class="holder">
|
||||||
|
|
|
@ -76,7 +76,7 @@ export const TopicView = (props: TopicProps) => {
|
||||||
<div class={styles.topicPage}>
|
<div class={styles.topicPage}>
|
||||||
<Show when={topic()}>
|
<Show when={topic()}>
|
||||||
<FullTopic topic={topic()} />
|
<FullTopic topic={topic()} />
|
||||||
<div class="container">
|
<div class="wide-container">
|
||||||
<div class={clsx(styles.groupControls, 'row group__controls')}>
|
<div class={clsx(styles.groupControls, 'row group__controls')}>
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<ul class="view-switcher">
|
<ul class="view-switcher">
|
||||||
|
|
|
@ -23,6 +23,8 @@ const pseudonames = {
|
||||||
authors: 'authors'
|
authors: 'authors'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nos = (s) => s.slice(-1)
|
||||||
|
|
||||||
export const StatMetrics = (props: StatMetricsProps) => {
|
export const StatMetrics = (props: StatMetricsProps) => {
|
||||||
return (
|
return (
|
||||||
<div class={styles.statMetrics}>
|
<div class={styles.statMetrics}>
|
||||||
|
|
|
@ -5,21 +5,23 @@ export default gql`
|
||||||
loadChats(limit: $limit, offset: $offset) {
|
loadChats(limit: $limit, offset: $offset) {
|
||||||
error
|
error
|
||||||
chats {
|
chats {
|
||||||
title
|
id
|
||||||
description
|
|
||||||
updatedAt
|
|
||||||
messages {
|
messages {
|
||||||
id
|
id
|
||||||
author
|
|
||||||
body
|
body
|
||||||
replyTo
|
author
|
||||||
createdAt
|
}
|
||||||
|
admins {
|
||||||
|
slug
|
||||||
|
name
|
||||||
}
|
}
|
||||||
users {
|
users {
|
||||||
slug
|
slug
|
||||||
name
|
name
|
||||||
userpic
|
|
||||||
}
|
}
|
||||||
|
unread
|
||||||
|
description
|
||||||
|
updatedAt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,5 +180,8 @@
|
||||||
"view": "просмотр",
|
"view": "просмотр",
|
||||||
"zine": "журнал",
|
"zine": "журнал",
|
||||||
"shout": "пост",
|
"shout": "пост",
|
||||||
"discussion": "дискурс"
|
"discussion": "дискурс",
|
||||||
|
"Conversations": "Переписки",
|
||||||
|
"Groups": "Группы",
|
||||||
|
"All": "Все"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
import { createSignal } from 'solid-js'
|
|
||||||
import type { Chat } from '../graphql/types.gen'
|
|
||||||
import { apiClient } from '../utils/apiClient'
|
import { apiClient } from '../utils/apiClient'
|
||||||
|
|
||||||
export const [chats, setChats] = createSignal<Chat[]>([])
|
|
||||||
|
|
||||||
export const loadAuthorsBy = async (by?: any): Promise<void> => {
|
export const loadAuthorsBy = async (by?: any): Promise<void> => {
|
||||||
return await apiClient.getAuthorsBy({ by })
|
return await apiClient.getAuthorsBy({ by })
|
||||||
}
|
}
|
||||||
|
|
212
src/styles/Article.module.scss
Normal file
212
src/styles/Article.module.scss
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
h1 {
|
||||||
|
@include font-size(4rem);
|
||||||
|
|
||||||
|
line-height: 1.1;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shoutHeader {
|
||||||
|
margin-bottom: 2em;
|
||||||
|
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
margin: 0 0 2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shoutCover {
|
||||||
|
background-size: cover;
|
||||||
|
height: 0;
|
||||||
|
padding-bottom: 56.2%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shoutBody {
|
||||||
|
font-size: 1.7rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
border-left: 4px solid;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: italic;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin: 1.5em 0;
|
||||||
|
padding: 0 0 0 1em;
|
||||||
|
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
margin-left: -16.6666%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mark {
|
||||||
|
background: none;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shoutAuthor,
|
||||||
|
.shoutDate {
|
||||||
|
@include font-size(1.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shoutAuthor {
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
|
||||||
|
a {
|
||||||
|
border: none;
|
||||||
|
color: rgb(0 0 0 / 60%);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shoutAuthorsList {
|
||||||
|
margin-top: 2em;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
color: #696969;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.writeComment {
|
||||||
|
border: 2px solid #f6f6f6;
|
||||||
|
@include font-size(1.7rem);
|
||||||
|
|
||||||
|
outline: none;
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: #858585;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.commentWarning {
|
||||||
|
background: #f6f6f6;
|
||||||
|
@include font-size(2.2rem);
|
||||||
|
|
||||||
|
margin-bottom: 1em;
|
||||||
|
padding: 2.4rem 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topic a {
|
||||||
|
/* white-space: nowrap; */
|
||||||
|
color: black;
|
||||||
|
padding: 0.3vh;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shoutStats {
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
border-top: 4px solid #000;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding: 3.2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shoutStatsItem {
|
||||||
|
@include font-size(1.7rem);
|
||||||
|
|
||||||
|
font-weight: 500;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 3.2rem 1em 0;
|
||||||
|
vertical-align: baseline;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 0.2em;
|
||||||
|
transition: filter 0.2s;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.icon {
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shoutStatsItemLikes {
|
||||||
|
.icon {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon:last-of-type {
|
||||||
|
// transform: rotate(180deg);
|
||||||
|
transform-origin: center;
|
||||||
|
margin-left: 0.3em;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shoutStatsItemAdditionalData {
|
||||||
|
color: rgb(0 0 0 / 40%);
|
||||||
|
font-weight: normal;
|
||||||
|
justify-self: flex-end;
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shoutStatsItemAdditionalDataItem {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topicsList {
|
||||||
|
@include font-size(1.2rem);
|
||||||
|
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
margin: 1.6rem 0;
|
||||||
|
|
||||||
|
.shoutTopic {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0.8rem 0.8rem 0.8rem 0;
|
||||||
|
|
||||||
|
a {
|
||||||
|
background: #f6f6f6;
|
||||||
|
color: #000;
|
||||||
|
border: none;
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgb(0 0 0 / 20%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,204 +0,0 @@
|
||||||
.article-page {
|
|
||||||
h1 {
|
|
||||||
@include font-size(4rem);
|
|
||||||
|
|
||||||
line-height: 1.1;
|
|
||||||
margin-top: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
line-height: 1.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shout__header {
|
|
||||||
margin-bottom: 2em;
|
|
||||||
|
|
||||||
@include media-breakpoint-up(md) {
|
|
||||||
margin: 0 0 2em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shout__cover {
|
|
||||||
background-size: cover;
|
|
||||||
height: 0;
|
|
||||||
padding-bottom: 56.2%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shout__body {
|
|
||||||
font-size: 1.7rem;
|
|
||||||
line-height: 1.6;
|
|
||||||
|
|
||||||
img {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
|
||||||
border-left: 4px solid;
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: italic;
|
|
||||||
line-height: 1.4;
|
|
||||||
margin: 1.5em 0;
|
|
||||||
padding: 0 0 0 1em;
|
|
||||||
|
|
||||||
@include media-breakpoint-up(md) {
|
|
||||||
margin-left: -16.6666%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mark {
|
|
||||||
background: none;
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shout__author,
|
|
||||||
.shout__date {
|
|
||||||
@include font-size(1.5rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.shout__author {
|
|
||||||
margin-bottom: 1.5em;
|
|
||||||
|
|
||||||
a {
|
|
||||||
border: none;
|
|
||||||
color: rgb(0 0 0 / 60%);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shout__authors-list {
|
|
||||||
margin-top: 2em;
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
color: #696969;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.write-comment {
|
|
||||||
border: 2px solid #f6f6f6;
|
|
||||||
@include font-size(1.7rem);
|
|
||||||
|
|
||||||
outline: none;
|
|
||||||
padding: 0.2em 0.4em;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: #858585;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-warning {
|
|
||||||
background: #f6f6f6;
|
|
||||||
@include font-size(2.2rem);
|
|
||||||
|
|
||||||
margin-bottom: 1em;
|
|
||||||
padding: 2.4rem 1.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topic a {
|
|
||||||
/* white-space: nowrap; */
|
|
||||||
color: black;
|
|
||||||
padding: 0.3vh;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shout-stats {
|
|
||||||
border-bottom: 1px solid #e8e8e8;
|
|
||||||
border-top: 4px solid #000;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
padding: 3.2rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shout-stats__item {
|
|
||||||
@include font-size(1.7rem);
|
|
||||||
|
|
||||||
font-weight: 500;
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 3.2rem 1em 0;
|
|
||||||
vertical-align: baseline;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: 0.2em;
|
|
||||||
transition: filter 0.2s;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.icon {
|
|
||||||
filter: invert(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shout-stats__item--likes {
|
|
||||||
.icon {
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon:last-of-type {
|
|
||||||
// transform: rotate(180deg);
|
|
||||||
transform-origin: center;
|
|
||||||
margin-left: 0.3em;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shout-stats__item--date {
|
|
||||||
color: rgb(0 0 0 / 40%);
|
|
||||||
font-weight: normal;
|
|
||||||
justify-self: flex-end;
|
|
||||||
margin-right: 0;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topics-list {
|
|
||||||
@include font-size(1.2rem);
|
|
||||||
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
margin: 1.6rem 0;
|
|
||||||
|
|
||||||
.shout__topic {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0.8rem 0.8rem 0.8rem 0;
|
|
||||||
|
|
||||||
a {
|
|
||||||
background: #f6f6f6;
|
|
||||||
color: #000;
|
|
||||||
border: none;
|
|
||||||
padding: 0.4rem 0.8rem;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
text-transform: uppercase;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rgb(0 0 0 / 20%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,11 +5,6 @@ main {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: добавлять когда открыт чат
|
|
||||||
body {
|
|
||||||
//overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages {
|
.messages {
|
||||||
top: 74px;
|
top: 74px;
|
||||||
height: calc(100% - 74px);
|
height: calc(100% - 74px);
|
||||||
|
@ -24,7 +19,7 @@ body {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 9;
|
z-index: 9;
|
||||||
|
|
||||||
> .row {
|
.row {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +166,7 @@ body {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 2px 0 12px 0;
|
padding: 2px 0 12px 0;
|
||||||
|
|
||||||
> .wrapper {
|
.wrapper {
|
||||||
border: 2px solid #cccccc;
|
border: 2px solid #cccccc;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
|
@ -179,7 +174,7 @@ body {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
> .grow-wrap {
|
.grow-wrap {
|
||||||
display: grid;
|
display: grid;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
@ -190,7 +185,7 @@ body {
|
||||||
transition: height 1.3s ease-in-out;
|
transition: height 1.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > textarea {
|
& textarea {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -207,7 +202,7 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after,
|
&::after,
|
||||||
& > textarea {
|
& textarea {
|
||||||
/* Identical styling required!! */
|
/* Identical styling required!! */
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
@ -218,7 +213,7 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> button {
|
button {
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -232,7 +227,7 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .icon {
|
.icon {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
|
|
|
@ -4,7 +4,12 @@ import type {
|
||||||
ShoutInput,
|
ShoutInput,
|
||||||
Topic,
|
Topic,
|
||||||
Author,
|
Author,
|
||||||
LoadShoutsOptions
|
LoadShoutsOptions,
|
||||||
|
QueryLoadChatsArgs,
|
||||||
|
QueryLoadAuthorsByArgs,
|
||||||
|
QueryLoadMessagesByArgs,
|
||||||
|
MutationCreateChatArgs,
|
||||||
|
MutationCreateMessageArgs
|
||||||
} from '../graphql/types.gen'
|
} from '../graphql/types.gen'
|
||||||
import { publicGraphQLClient } from '../graphql/publicGraphQLClient'
|
import { publicGraphQLClient } from '../graphql/publicGraphQLClient'
|
||||||
import { getToken, privateGraphQLClient } from '../graphql/privateGraphQLClient'
|
import { getToken, privateGraphQLClient } from '../graphql/privateGraphQLClient'
|
||||||
|
@ -29,7 +34,6 @@ import chatMessagesLoadBy from '../graphql/query/chat-messages-load-by'
|
||||||
import authorBySlug from '../graphql/query/author-by-slug'
|
import authorBySlug from '../graphql/query/author-by-slug'
|
||||||
import topicBySlug from '../graphql/query/topic-by-slug'
|
import topicBySlug from '../graphql/query/topic-by-slug'
|
||||||
import createChat from '../graphql/mutation/create-chat'
|
import createChat from '../graphql/mutation/create-chat'
|
||||||
import createMessage from '../graphql/mutation/create-chat-message'
|
|
||||||
import reactionsLoadBy from '../graphql/query/reactions-load-by'
|
import reactionsLoadBy from '../graphql/query/reactions-load-by'
|
||||||
import { REACTIONS_AMOUNT_PER_PAGE } from '../stores/zine/reactions'
|
import { REACTIONS_AMOUNT_PER_PAGE } from '../stores/zine/reactions'
|
||||||
import authorsLoadBy from '../graphql/query/authors-load-by'
|
import authorsLoadBy from '../graphql/query/authors-load-by'
|
||||||
|
@ -221,12 +225,12 @@ export const apiClient = {
|
||||||
|
|
||||||
// CUDL
|
// CUDL
|
||||||
|
|
||||||
createChat: async ({ title, members }) => {
|
createChat: async (options: MutationCreateChatArgs) => {
|
||||||
return await privateGraphQLClient.mutation(createChat, { title: title, members: members }).toPromise()
|
return await privateGraphQLClient.mutation(createChat, options).toPromise()
|
||||||
},
|
},
|
||||||
|
|
||||||
createMessage: async ({ chat, body }) => {
|
createMessage: async (options: MutationCreateMessageArgs) => {
|
||||||
return await privateGraphQLClient.mutation(createChat, { chat: chat, body: body }).toPromise()
|
return await privateGraphQLClient.mutation(createChat, options).toPromise()
|
||||||
},
|
},
|
||||||
|
|
||||||
updateReaction: async ({ reaction }) => {
|
updateReaction: async ({ reaction }) => {
|
||||||
|
@ -239,8 +243,8 @@ export const apiClient = {
|
||||||
|
|
||||||
return response.data.deleteReaction
|
return response.data.deleteReaction
|
||||||
},
|
},
|
||||||
getAuthorsBy: async ({ by, limit = 50, offset = 0 }) => {
|
getAuthorsBy: async (options: QueryLoadAuthorsByArgs) => {
|
||||||
const resp = await publicGraphQLClient.query(authorsLoadBy, { by, limit, offset }).toPromise()
|
const resp = await publicGraphQLClient.query(authorsLoadBy, options).toPromise()
|
||||||
return resp.data.loadAuthorsBy
|
return resp.data.loadAuthorsBy
|
||||||
},
|
},
|
||||||
getShout: async (slug: string) => {
|
getShout: async (slug: string) => {
|
||||||
|
@ -263,30 +267,22 @@ export const apiClient = {
|
||||||
},
|
},
|
||||||
getReactionsBy: async ({ by, limit = REACTIONS_AMOUNT_PER_PAGE, offset = 0 }) => {
|
getReactionsBy: async ({ by, limit = REACTIONS_AMOUNT_PER_PAGE, offset = 0 }) => {
|
||||||
const resp = await publicGraphQLClient.query(reactionsLoadBy, { by, limit, offset }).toPromise()
|
const resp = await publicGraphQLClient.query(reactionsLoadBy, { by, limit, offset }).toPromise()
|
||||||
console.error(resp)
|
if (resp.error) {
|
||||||
|
console.error(resp.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
return resp.data.loadReactionsBy
|
return resp.data.loadReactionsBy
|
||||||
},
|
},
|
||||||
|
|
||||||
// inbox
|
// inbox
|
||||||
getChats: async ({ limit, offset }) => {
|
getChats: async (options: QueryLoadChatsArgs) => {
|
||||||
const resp = await privateGraphQLClient.query(myChats, { limit, offset }).toPromise()
|
const resp = await privateGraphQLClient.query(myChats, options).toPromise()
|
||||||
console.log('!!! resp.data.myChats:', resp)
|
console.debug('[getChats]', resp)
|
||||||
return resp.data.myChats
|
return resp.data.myChats
|
||||||
},
|
},
|
||||||
|
|
||||||
getChatMessages: async ({
|
getChatMessages: async (options: QueryLoadMessagesByArgs) => {
|
||||||
chat,
|
const resp = await privateGraphQLClient.query(chatMessagesLoadBy, options).toPromise()
|
||||||
limit = 50,
|
|
||||||
offset = 0
|
|
||||||
}: {
|
|
||||||
chat: string
|
|
||||||
limit?: number
|
|
||||||
offset?: number
|
|
||||||
}) => {
|
|
||||||
const by = {
|
|
||||||
chat
|
|
||||||
}
|
|
||||||
const resp = await privateGraphQLClient.query(chatMessagesLoadBy, { by, offset, limit }).toPromise()
|
|
||||||
return resp.data.loadChat
|
return resp.data.loadChat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export const isDev = import.meta.env.MODE === 'development'
|
export const isDev = import.meta.env.MODE === 'development'
|
||||||
|
|
||||||
const defaultApiUrl = 'https://v2.discours.io'
|
const defaultApiUrl = 'https://testapi.discours.io'
|
||||||
export const apiBaseUrl = import.meta.env.PUBLIC_API_URL || defaultApiUrl
|
export const apiBaseUrl = import.meta.env.PUBLIC_API_URL || defaultApiUrl
|
||||||
|
|
Loading…
Reference in New Issue
Block a user