Merge remote-tracking branch 'origin/dev' into prepare-inbox
# Conflicts: # src/components/Inbox/Message.module.scss # src/components/Views/Inbox.tsx # src/styles/Inbox.scss # src/utils/config.ts
This commit is contained in:
commit
6e21d0640b
11
README.md
11
README.md
|
@ -1,11 +1,4 @@
|
|||
# Astro + Solid.js
|
||||
|
||||
Created with
|
||||
|
||||
```
|
||||
npm init astro -- --template framework-solid
|
||||
yarn install
|
||||
npm start
|
||||
```
|
||||
|
||||
Astro working with [Solid](https://www.solidjs.com/).
|
||||
|
||||
Write your Solid components as `.jsx` or `.tsx` files in your project.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { defineConfig, AstroUserConfig } from 'astro/config'
|
||||
import vercel from '@astrojs/vercel/serverless'
|
||||
import solidJs from '@astrojs/solid-js'
|
||||
import type { CSSOptions, PluginOption } from 'vite'
|
||||
import type { CSSOptions } from 'vite'
|
||||
import defaultGenerateScopedName from 'postcss-modules/build/generateScopedName'
|
||||
import { isDev } from './src/utils/config'
|
||||
import { visualizer } from 'rollup-plugin-visualizer'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
overwrite: true
|
||||
schema: 'https://testapi.discours.io/graphql'
|
||||
schema: 'http://v2.discours.io/graphql'
|
||||
generates:
|
||||
src/graphql/introspec.gen.ts:
|
||||
plugins:
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
"astro": "^1.6.8",
|
||||
"astro-eslint-parser": "^0.9.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bootstrap": "5.1.3",
|
||||
"bootstrap": "5.2.2",
|
||||
"clsx": "^1.2.1",
|
||||
"cookie": "^0.5.0",
|
||||
"cookie-signature": "^1.2.0",
|
||||
|
|
10965
pnpm-lock.yaml
10965
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -20,6 +20,7 @@ interface AuthorCardProps {
|
|||
isAuthorPage?: boolean
|
||||
noSocialButtons?: boolean
|
||||
isAuthorsList?: boolean
|
||||
truncateBio?: boolean
|
||||
}
|
||||
|
||||
export const AuthorCard = (props: AuthorCardProps) => {
|
||||
|
@ -63,7 +64,8 @@ export const AuthorCard = (props: AuthorCardProps) => {
|
|||
</Show>
|
||||
|
||||
<Show when={!props.hideDescription}>
|
||||
<div class={styles.authorAbout} classList={{ 'text-truncate': props.isAuthorsList }}>
|
||||
{props.isAuthorsList}
|
||||
<div class={styles.authorAbout} classList={{ 'text-truncate': props.truncateBio }}>
|
||||
{bio()}
|
||||
</div>
|
||||
</Show>
|
||||
|
@ -86,7 +88,6 @@ export const AuthorCard = (props: AuthorCardProps) => {
|
|||
>
|
||||
<Show when={!props.isAuthorsList}>
|
||||
<Icon name="author-subscribe" class={styles.icon} />
|
||||
|
||||
</Show>
|
||||
<span class={styles.buttonLabel}>{t('Follow')}</span>
|
||||
</button>
|
||||
|
@ -103,7 +104,6 @@ export const AuthorCard = (props: AuthorCardProps) => {
|
|||
>
|
||||
<Show when={!props.isAuthorsList}>
|
||||
<Icon name="author-unsubscribe" class={styles.icon} />
|
||||
|
||||
</Show>
|
||||
<span class={styles.buttonLabel}>{t('Unfollow')}</span>
|
||||
</button>
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
@include font-size(1.5rem);
|
||||
|
||||
font-weight: 500;
|
||||
padding-right: 0.3em;
|
||||
white-space: nowrap;
|
||||
|
||||
img {
|
||||
|
@ -108,3 +109,7 @@ button.follow {
|
|||
max-width: 2em;
|
||||
max-height: 2em;
|
||||
}
|
||||
|
||||
.shoutCardContainer {
|
||||
display: flex;
|
||||
}
|
||||
|
|
|
@ -4,10 +4,11 @@ import { For, Show } from 'solid-js'
|
|||
import { ArticleCard } from './Card'
|
||||
import { AuthorCard } from '../Author/Card'
|
||||
import { TopicCard } from '../Topic/Card'
|
||||
import style from './Beside.module.scss'
|
||||
import styles from './Beside.module.scss'
|
||||
import type { Author, Shout, Topic, User } from '../../graphql/types.gen'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
import { t } from '../../utils/intl'
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
interface BesideProps {
|
||||
title?: string
|
||||
|
@ -29,21 +30,28 @@ export const Beside = (props: BesideProps) => {
|
|||
<Show when={!!props.values}>
|
||||
<div class="col-md-4">
|
||||
<Show when={!!props.title}>
|
||||
<div class={style.besideColumnTitle}>
|
||||
<div class={styles.besideColumnTitle}>
|
||||
<h4>{props.title}</h4>
|
||||
|
||||
<Show when={props.wrapper === 'author'}>
|
||||
<a href="/user/list">
|
||||
<a href="/authors">
|
||||
{t('All authors')}
|
||||
<Icon name="arrow-right" />
|
||||
<Icon name="arrow-right" class={styles.icon} />
|
||||
</a>
|
||||
</Show>
|
||||
|
||||
<Show when={props.wrapper === 'topic'}>
|
||||
<a href="/topics">
|
||||
{t('All topics')}
|
||||
<Icon name="arrow-right" class={styles.icon} />
|
||||
</a>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
<ul class={style.besideColumn}>
|
||||
<ul class={styles.besideColumn}>
|
||||
<For each={[...props.values]}>
|
||||
{(value: Partial<Shout | User | Topic>) => (
|
||||
<li classList={{ [style.top]: props.wrapper.startsWith('top-') }}>
|
||||
<li classList={{ [styles.top]: props.wrapper.startsWith('top-') }}>
|
||||
<Show when={props.wrapper === 'topic'}>
|
||||
<TopicCard
|
||||
topic={value as Topic}
|
||||
|
@ -51,10 +59,16 @@ export const Beside = (props: BesideProps) => {
|
|||
shortDescription={props.topicShortDescription}
|
||||
isTopicInRow={props.isTopicInRow}
|
||||
iconButton={props.iconButton}
|
||||
showPublications={true}
|
||||
/>
|
||||
</Show>
|
||||
<Show when={props.wrapper === 'author'}>
|
||||
<AuthorCard author={value as Author} compact={true} hasLink={true} />
|
||||
<AuthorCard
|
||||
author={value as Author}
|
||||
compact={true}
|
||||
hasLink={true}
|
||||
truncateBio={true}
|
||||
/>
|
||||
</Show>
|
||||
<Show when={props.wrapper === 'article' && value?.slug}>
|
||||
<ArticleCard article={value as Shout} settings={{ noimage: true }} />
|
||||
|
@ -71,8 +85,8 @@ export const Beside = (props: BesideProps) => {
|
|||
</ul>
|
||||
</div>
|
||||
</Show>
|
||||
<div class="col-md-8">
|
||||
<ArticleCard article={props.beside} settings={{ isBigTitle: true }} />
|
||||
<div class={clsx('col-md-8', styles.shoutCardContainer)}>
|
||||
<ArticleCard article={props.beside} settings={{ isBigTitle: true, isBeside: true }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -675,3 +675,19 @@
|
|||
@include font-size(2.4rem);
|
||||
}
|
||||
}
|
||||
|
||||
.shoutCardBeside {
|
||||
&,
|
||||
.shoutCardCoverContainer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.shoutCardCover {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.shoutCardContent {
|
||||
padding-top: 1.6rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import { translit } from '../../utils/ru2en'
|
|||
import { Icon } from '../_shared/Icon'
|
||||
import styles from './Card.module.scss'
|
||||
import { locale } from '../../stores/ui'
|
||||
import { handleClientRouteLinkClick } from '../../stores/router'
|
||||
import { clsx } from 'clsx'
|
||||
import CardTopic from './CardTopic'
|
||||
|
||||
|
@ -29,6 +28,7 @@ interface ArticleCardProps {
|
|||
withBorder?: boolean
|
||||
isCompact?: boolean
|
||||
isSingle?: boolean
|
||||
isBeside?: boolean
|
||||
}
|
||||
article: Shout
|
||||
}
|
||||
|
@ -82,7 +82,8 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
|||
[styles.shoutCardVertical]: props.settings?.isVertical,
|
||||
[styles.shoutCardWithBorder]: props.settings?.withBorder,
|
||||
[styles.shoutCardCompact]: props.settings?.isCompact,
|
||||
[styles.shoutCardSingle]: props.settings?.isSingle
|
||||
[styles.shoutCardSingle]: props.settings?.isSingle,
|
||||
[styles.shoutCardBeside]: props.settings?.isBeside
|
||||
}}
|
||||
>
|
||||
<Show when={!props.settings?.noimage && cover}>
|
||||
|
@ -113,7 +114,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
|||
</Show>
|
||||
|
||||
<div class={styles.shoutCardTitlesContainer}>
|
||||
<a href={`/${slug || ''}`} onClick={handleClientRouteLinkClick}>
|
||||
<a href={`/${slug || ''}`}>
|
||||
<div class={styles.shoutCardTitle}>
|
||||
<span class={styles.shoutCardLinkContainer}>{title}</span>
|
||||
</div>
|
||||
|
|
|
@ -23,7 +23,7 @@ export const Row2 = (props: { articles: Shout[]; isEqual?: boolean }) => {
|
|||
<div class={`col-md-${props.isEqual ? '6' : x[y()][i()]}`}>
|
||||
<ArticleCard
|
||||
article={a}
|
||||
settings={{ isWithCover: props.isEqual || x[y()][i()] === '8' }}
|
||||
settings={{ isWithCover: props.isEqual || x[y()][i()] === '8', nodate: props.isEqual }}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { clsx } from 'clsx'
|
|||
import { t } from '../../../utils/intl'
|
||||
import { hideModal } from '../../../stores/ui'
|
||||
import { createMemo, createSignal, onMount, Show } from 'solid-js'
|
||||
import { handleClientRouteLinkClick, useRouter } from '../../../stores/router'
|
||||
import { useRouter } from '../../../stores/router'
|
||||
import type { ConfirmEmailSearchParams } from './types'
|
||||
import { ApiError } from '../../../utils/apiClient'
|
||||
import { useSession } from '../../../context/session'
|
||||
|
@ -48,7 +48,7 @@ export const EmailConfirm = () => {
|
|||
<Show when={isTokenExpired()}>
|
||||
<div class={styles.title}>Ссылка больше не действительна</div>
|
||||
<div class={styles.text}>
|
||||
<a href="/?modal=auth&mode=login" class={styles.sendLink} onClick={handleClientRouteLinkClick}>
|
||||
<a href="/?modal=auth&mode=login" class={styles.sendLink}>
|
||||
{/*TODO: temp solution, should be send link again, but we don't have email here*/}
|
||||
Вход
|
||||
</a>
|
||||
|
@ -57,7 +57,7 @@ export const EmailConfirm = () => {
|
|||
<Show when={isTokenInvalid()}>
|
||||
<div class={styles.title}>Неправильная ссылка</div>
|
||||
<div class={styles.text}>
|
||||
<a href="/?modal=auth&mode=login" class={styles.sendLink} onClick={handleClientRouteLinkClick}>
|
||||
<a href="/?modal=auth&mode=login" class={styles.sendLink}>
|
||||
{/*TODO: temp solution, should be send link again, but we don't have email here*/}
|
||||
Вход
|
||||
</a>
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Dynamic } from 'solid-js/web'
|
|||
import { Component, createEffect, createMemo } from 'solid-js'
|
||||
import { t } from '../../../utils/intl'
|
||||
import { hideModal } from '../../../stores/ui'
|
||||
import { handleClientRouteLinkClick, useRouter } from '../../../stores/router'
|
||||
import { useRouter } from '../../../stores/router'
|
||||
import { clsx } from 'clsx'
|
||||
import styles from './AuthModal.module.scss'
|
||||
import { LoginForm } from './LoginForm'
|
||||
|
@ -57,9 +57,8 @@ export const AuthModal = () => {
|
|||
{t('By signing up you agree with our')}{' '}
|
||||
<a
|
||||
href="/about/terms-of-use"
|
||||
onClick={(event) => {
|
||||
onClick={() => {
|
||||
hideModal()
|
||||
handleClientRouteLinkClick(event)
|
||||
}}
|
||||
>
|
||||
{t('terms of use')}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Modal } from './Modal'
|
|||
import { AuthModal } from './AuthModal'
|
||||
import { t } from '../../utils/intl'
|
||||
import { useModalStore } from '../../stores/ui'
|
||||
import { handleClientRouteLinkClick, router, Routes, useRouter } from '../../stores/router'
|
||||
import { router, Routes, useRouter } from '../../stores/router'
|
||||
import styles from './Header.module.scss'
|
||||
import { getPagePath } from '@nanostores/router'
|
||||
import { clsx } from 'clsx'
|
||||
|
@ -91,7 +91,7 @@ export const Header = (props: Props) => {
|
|||
<div class={clsx(styles.mainHeaderInner, 'wide-container')}>
|
||||
<nav class={clsx(styles.headerInner, 'row')} classList={{ fixed: fixed() }}>
|
||||
<div class={clsx(styles.mainLogo, 'col-auto')}>
|
||||
<a href={getPagePath(router, 'home')} onClick={handleClientRouteLinkClick}>
|
||||
<a href={getPagePath(router, 'home')}>
|
||||
<img src="/logo.svg" alt={t('Discours')} />
|
||||
</a>
|
||||
</div>
|
||||
|
@ -107,9 +107,7 @@ export const Header = (props: Props) => {
|
|||
<For each={resources}>
|
||||
{(r) => (
|
||||
<li classList={{ [styles.selected]: r.route === page().route }}>
|
||||
<a href={getPagePath(router, r.route, null)} onClick={handleClientRouteLinkClick}>
|
||||
{r.name}
|
||||
</a>
|
||||
<a href={getPagePath(router, r.route, null)}>{r.name}</a>
|
||||
</li>
|
||||
)}
|
||||
</For>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import styles from './Header.module.scss'
|
||||
import { clsx } from 'clsx'
|
||||
import { handleClientRouteLinkClick, useRouter } from '../../stores/router'
|
||||
import { useRouter } from '../../stores/router'
|
||||
import { t } from '../../utils/intl'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
import { createSignal, Show } from 'solid-js'
|
||||
|
@ -9,7 +9,7 @@ import { ProfilePopup } from './ProfilePopup'
|
|||
import Userpic from '../Author/Userpic'
|
||||
import type { Author } from '../../graphql/types.gen'
|
||||
import { showModal, useWarningsStore } from '../../stores/ui'
|
||||
import { ClientContainer } from '../_shared/ClientContainer'
|
||||
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
|
||||
import { useSession } from '../../context/session'
|
||||
|
||||
type HeaderAuthProps = {
|
||||
|
@ -37,12 +37,12 @@ export const HeaderAuth = (props: HeaderAuthProps) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<ClientContainer>
|
||||
<ShowOnlyOnClient>
|
||||
<Show when={!session.loading}>
|
||||
<div class={styles.usernav}>
|
||||
<div class={clsx(styles.userControl, styles.userControl, 'col')}>
|
||||
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
|
||||
<a href="/create" onClick={handleClientRouteLinkClick}>
|
||||
<a href="/create">
|
||||
<span class={styles.textLabel}>{t('Create post')}</span>
|
||||
<Icon name="pencil" class={styles.icon} />
|
||||
</a>
|
||||
|
@ -68,7 +68,7 @@ export const HeaderAuth = (props: HeaderAuthProps) => {
|
|||
when={isAuthenticated()}
|
||||
fallback={
|
||||
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose, 'loginbtn')}>
|
||||
<a href="?modal=auth&mode=login" onClick={handleClientRouteLinkClick}>
|
||||
<a href="?modal=auth&mode=login">
|
||||
<span class={styles.textLabel}>{t('Enter')}</span>
|
||||
<Icon name="user-anonymous" class={styles.icon} />
|
||||
</a>
|
||||
|
@ -102,6 +102,6 @@ export const HeaderAuth = (props: HeaderAuthProps) => {
|
|||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</ClientContainer>
|
||||
</ShowOnlyOnClient>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import { Icon } from '../_shared/Icon'
|
|||
import './Topics.scss'
|
||||
import { t } from '../../utils/intl'
|
||||
import { locale } from '../../stores/ui'
|
||||
import { handleClientRouteLinkClick } from '../../stores/router'
|
||||
|
||||
export const NavTopics = (props: { topics: Topic[] }) => {
|
||||
const tag = (topic: Topic) =>
|
||||
|
@ -18,7 +17,7 @@ export const NavTopics = (props: { topics: Topic[] }) => {
|
|||
<For each={props.topics}>
|
||||
{(topic) => (
|
||||
<li class="item">
|
||||
<a href={`/topic/${topic.slug}`} onClick={handleClientRouteLinkClick}>
|
||||
<a href={`/topic/${topic.slug}`}>
|
||||
<span>#{tag(topic)}</span>
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { PageWrap } from '../_shared/PageWrap'
|
||||
import { ArticleView } from '../Views/Article'
|
||||
import type { PageProps } from '../types'
|
||||
import { loadShoutsBy, useArticlesStore } from '../../stores/zine/articles'
|
||||
import { loadShout, useArticlesStore } from '../../stores/zine/articles'
|
||||
import { createMemo, onMount, Show } from 'solid-js'
|
||||
import type { Shout } from '../../graphql/types.gen'
|
||||
import { useRouter } from '../../stores/router'
|
||||
|
@ -32,7 +32,7 @@ export const ArticlePage = (props: PageProps) => {
|
|||
const articleValue = articleEntities()[slug()]
|
||||
|
||||
if (!articleValue || !articleValue.body) {
|
||||
await loadShoutsBy({ by: { slug: slug() }, limit: 1, offset: 0 })
|
||||
await loadShout(slug())
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { PageWrap } from '../_shared/PageWrap'
|
|||
import { AuthorView, PRERENDERED_ARTICLES_COUNT } from '../Views/Author'
|
||||
import type { PageProps } from '../types'
|
||||
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||
import { loadShoutsBy, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { loadShouts, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { useRouter } from '../../stores/router'
|
||||
import { loadAuthor } from '../../stores/zine/authors'
|
||||
import { Loading } from '../Loading'
|
||||
|
@ -27,7 +27,7 @@ export const AuthorPage = (props: PageProps) => {
|
|||
return
|
||||
}
|
||||
|
||||
await loadShoutsBy({ by: { author: slug() }, limit: PRERENDERED_ARTICLES_COUNT })
|
||||
await loadShouts({ filters: { author: slug() }, limit: PRERENDERED_ARTICLES_COUNT })
|
||||
await loadAuthor({ slug: slug() })
|
||||
|
||||
setIsLoaded(true)
|
||||
|
|
3
src/components/Pages/HomePage.module.scss
Normal file
3
src/components/Pages/HomePage.module.scss
Normal file
|
@ -0,0 +1,3 @@
|
|||
.mainContent {
|
||||
padding-top: 100px;
|
||||
}
|
|
@ -2,9 +2,10 @@ import { HomeView, PRERENDERED_ARTICLES_COUNT } from '../Views/Home'
|
|||
import { PageWrap } from '../_shared/PageWrap'
|
||||
import type { PageProps } from '../types'
|
||||
import { createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||
import { loadShoutsBy, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { loadShouts, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { loadRandomTopics } from '../../stores/zine/topics'
|
||||
import { Loading } from '../Loading'
|
||||
import styles from './HomePage.module.scss'
|
||||
|
||||
export const HomePage = (props: PageProps) => {
|
||||
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.shouts) && Boolean(props.randomTopics))
|
||||
|
@ -14,7 +15,7 @@ export const HomePage = (props: PageProps) => {
|
|||
return
|
||||
}
|
||||
|
||||
await loadShoutsBy({ by: { visibility: 'public' }, limit: PRERENDERED_ARTICLES_COUNT, offset: 0 })
|
||||
await loadShouts({ filters: { visibility: 'public' }, limit: PRERENDERED_ARTICLES_COUNT, offset: 0 })
|
||||
await loadRandomTopics()
|
||||
|
||||
setIsLoaded(true)
|
||||
|
@ -23,7 +24,7 @@ export const HomePage = (props: PageProps) => {
|
|||
onCleanup(() => resetSortedArticles())
|
||||
|
||||
return (
|
||||
<PageWrap>
|
||||
<PageWrap class={styles.mainContent}>
|
||||
<Show when={isLoaded()} fallback={<Loading />}>
|
||||
<HomeView randomTopics={props.randomTopics} shouts={props.shouts || []} />
|
||||
</Show>
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { PageWrap } from '../_shared/PageWrap'
|
||||
import type { PageProps } from '../types'
|
||||
import { createMemo, createSignal, For, onCleanup, onMount, Show } from 'solid-js'
|
||||
import { loadShoutsBy, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { loadShouts, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { useRouter } from '../../stores/router'
|
||||
import { LayoutType, useLayoutsStore } from '../../stores/zine/layouts'
|
||||
import { Loading } from '../Loading'
|
||||
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
|
||||
import type { Shout } from '../../graphql/types.gen'
|
||||
import { splitToPages } from '../../utils/splitToPages'
|
||||
import clsx from 'clsx'
|
||||
import { clsx } from 'clsx'
|
||||
import { t } from '../../utils/intl'
|
||||
import { Row3 } from '../Feed/Row3'
|
||||
import { Row2 } from '../Feed/Row2'
|
||||
|
@ -33,7 +33,8 @@ export const LayoutShoutsPage = (props: PageProps) => {
|
|||
const loadMoreLayout = async (kind: LayoutType) => {
|
||||
saveScrollPosition()
|
||||
const { hasMore } = await loadLayoutShoutsBy({
|
||||
by: { layout: kind },
|
||||
// filters: { layout: kind },
|
||||
|
||||
limit: LOAD_MORE_PAGE_SIZE,
|
||||
offset: sortedArticles().length
|
||||
})
|
||||
|
@ -62,7 +63,7 @@ export const LayoutShoutsPage = (props: PageProps) => {
|
|||
|
||||
onMount(async () => {
|
||||
if (!isLoaded()) {
|
||||
await loadShoutsBy({ by: { layout: layout() }, limit: PRERENDERED_ARTICLES_COUNT, offset: 0 })
|
||||
await loadShouts({ filters: { layout: layout() }, limit: PRERENDERED_ARTICLES_COUNT, offset: 0 })
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ import { PageWrap } from '../_shared/PageWrap'
|
|||
import { SearchView } from '../Views/Search'
|
||||
import type { PageProps } from '../types'
|
||||
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||
import { loadShoutsBy, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { useRouter } from '../../stores/router'
|
||||
import { loadShouts, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { Loading } from '../Loading'
|
||||
|
||||
export const SearchPage = (props: PageProps) => {
|
||||
|
@ -26,7 +26,7 @@ export const SearchPage = (props: PageProps) => {
|
|||
return
|
||||
}
|
||||
|
||||
await loadShoutsBy({ by: { title: q(), body: q() }, limit: 50, offset: 0 })
|
||||
await loadShouts({ filters: { title: q(), body: q() }, limit: 50, offset: 0 })
|
||||
setIsLoaded(true)
|
||||
})
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { PageWrap } from '../_shared/PageWrap'
|
|||
import { PRERENDERED_ARTICLES_COUNT, TopicView } from '../Views/Topic'
|
||||
import type { PageProps } from '../types'
|
||||
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||
import { loadShoutsBy, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { loadShouts, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { useRouter } from '../../stores/router'
|
||||
import { loadTopic } from '../../stores/zine/topics'
|
||||
import { Loading } from '../Loading'
|
||||
|
@ -27,7 +27,7 @@ export const TopicPage = (props: PageProps) => {
|
|||
return
|
||||
}
|
||||
|
||||
await loadShoutsBy({ by: { topics: [slug()] }, limit: PRERENDERED_ARTICLES_COUNT, offset: 0 })
|
||||
await loadShouts({ filters: { topic: slug() }, limit: PRERENDERED_ARTICLES_COUNT, offset: 0 })
|
||||
await loadTopic({ slug: slug() })
|
||||
|
||||
setIsLoaded(true)
|
||||
|
|
|
@ -40,10 +40,7 @@ export const DogmaPage = () => {
|
|||
<li>
|
||||
<b>Всегда исправляем ошибки, если мы их допустили.</b>
|
||||
Никто не безгрешен, иногда и мы ошибаемся. Заметили ошибку - отправьте{' '}
|
||||
<a href="/about/guide#editing" target="_self">
|
||||
ремарку
|
||||
</a>{' '}
|
||||
автору или напишите нам на{' '}
|
||||
<a href="/about/guide#editing">ремарку</a> автору или напишите нам на{' '}
|
||||
<a href="mailto:welcome@discours.io" target="_blank">
|
||||
welcome@discours.io
|
||||
</a>
|
||||
|
|
|
@ -67,28 +67,28 @@ export const GuidePage = () => {
|
|||
|
||||
<p>
|
||||
Дискурс — независимый журнал о культуре, науке, искусстве и обществе
|
||||
с <a href="/about/manifest">открытой редакцией</a>. У нас нет главного редактора,
|
||||
инвестора и вообще никого, кто бы принимал единоличные решения. Вместо традиционных
|
||||
иерархий Дискурс основан на принципах прямой демократии: в нашем горизонтальном
|
||||
сообществе все редакционные вопросы решаются открытым голосованием авторов журнала. Вот как
|
||||
это работает.
|
||||
с
|
||||
<a href="/about/manifest">открытой редакцией</a>. У нас нет главного редактора, инвестора
|
||||
и вообще никого, кто бы принимал единоличные решения. Вместо традиционных иерархий
|
||||
Дискурс основан на принципах прямой демократии: в нашем горизонтальном сообществе
|
||||
все редакционные вопросы решаются открытым голосованием авторов журнала. Вот как это работает.
|
||||
</p>
|
||||
<h3 id="how-it-works">Как устроен сайт Дискурса</h3>
|
||||
<p>Дискурс состоит из четырех основных разделов:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>
|
||||
<a href="/topics">Темы</a> — у нас публикуются исследования, обзоры, эссе,
|
||||
интервью, репортажи, аналитика и другие материалы о культуре, науке, искусстве
|
||||
и обществе.
|
||||
<a href="/topics">Темы</a>
|
||||
— у нас публикуются исследования, обзоры, эссе, интервью, репортажи,
|
||||
аналитика и другие материалы о культуре, науке, искусстве и обществе.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<a href="/topic/art">Искусство</a> — здесь, например, представлены
|
||||
художественные произведения: литература, живопись, музыка, фотографии, видео. Этот раздел
|
||||
помогает прозвучать новому искусству, которое создают российские художники, писатели,
|
||||
режиссёры и музыканты.
|
||||
<a href="/topic/art">Искусство</a>
|
||||
— здесь, например, представлены художественные произведения: литература,
|
||||
живопись, музыка, фотографии, видео. Этот раздел помогает прозвучать новому искусству,
|
||||
которое создают российские художники, писатели, режиссёры и музыканты.
|
||||
</p>
|
||||
</li>
|
||||
{/*
|
||||
|
@ -118,14 +118,16 @@ export const GuidePage = () => {
|
|||
— ключевым словам, которые располагаются в конце материалов и связывают
|
||||
материалы по жанрам (например,
|
||||
<a href="/topic/interview">интервью</a>, <a href="/topic/reportage">репортажи</a>,{' '}
|
||||
<a href="/topic/essay">эссе</a>, <a href="/topic/likbez">ликбезы</a>), по тематике (
|
||||
<a href="/topic/cinema">кино</a>, <a href="/topic/philosophy">философия</a>,{' '}
|
||||
<a href="/topic/history">история</a>, <a href="/topic/absurdism">абсурдизм</a>,{' '}
|
||||
<a href="/topic/sex">секс</a> и т.д.) или в серии (как «
|
||||
<a href="/topic/zakony-mira">Законы мира</a>» или «
|
||||
<a href="/topic/za-liniey-mannergeyma">За линией Маннергейма</a>»). Темы объединяют
|
||||
сотни публикаций, помогают ориентироваться в журнале и следить за интересными
|
||||
материалами.
|
||||
<a href="/topic/essay">эссе</a>, <a href="/topic/likbez">ликбезы</a>
|
||||
), по тематике (<a href="/topic/cinema">кино</a>,{' '}
|
||||
<a href="/topic/philosophy">философия</a>, <a href="/topic/history">история</a>,{' '}
|
||||
<a href="/topic/absurdism">абсурдизм</a>, <a href="/topic/sex">секс</a> и т.д.) или
|
||||
в серии (как «
|
||||
<a href="/topic/zakony-mira">Законы мира</a>
|
||||
» или «
|
||||
<a href="/topic/za-liniey-mannergeyma">За линией Маннергейма</a>
|
||||
»). Темы объединяют сотни публикаций, помогают ориентироваться в журнале
|
||||
и следить за интересными материалами.
|
||||
</p>
|
||||
|
||||
<section>
|
||||
|
|
|
@ -91,10 +91,11 @@ export const ManifestPage = () => {
|
|||
<p>
|
||||
Редакция Дискурса открыта для всех: у нас нет цензуры, запретных тем
|
||||
и идеологических рамок. Каждый может <a href="/create">прислать материал</a>{' '}
|
||||
в журнал и <a href="/about/guide">присоединиться к редакции</a>. Предоставляя
|
||||
трибуну для независимой журналистики и художественных проектов, мы помогаем людям
|
||||
рассказывать свои истории так, чтобы они были услышаны. Мы убеждены: чем больше голосов
|
||||
будет звучать на Дискурсе, тем громче в полифонии мнений будет слышна истина.
|
||||
в журнал и
|
||||
<a href="/about/guide">присоединиться к редакции</a>. Предоставляя трибуну для
|
||||
независимой журналистики и художественных проектов, мы помогаем людям рассказывать
|
||||
свои истории так, чтобы они были услышаны. Мы убеждены: чем больше голосов будет звучать
|
||||
на Дискурсе, тем громче в полифонии мнений будет слышна истина.
|
||||
</p>
|
||||
|
||||
<h2 class="h2" id="participation">
|
||||
|
|
|
@ -269,7 +269,7 @@ export const TermsOfUsePage = () => {
|
|||
<a href="mailto:welcome@discours.io" target="_blank">
|
||||
welcome@discours.io
|
||||
</a>{' '}
|
||||
или через форму <a href="/feedback-idea">«предложить идею»</a>.
|
||||
или через форму <a href="/connect">«предложить идею»</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -76,11 +76,7 @@ export const ThanksPage = () => {
|
|||
признательны всем, кто нас поддерживает. Ваши пожертвования — финансовый фундамент
|
||||
журнала. Благодаря вам мы развиваем платформу качественной журналистики, которая помогает
|
||||
самым разным авторам быть услышанными. Стать нашим меценатом и подписаться
|
||||
на ежемесячную поддержку проекта можно{' '}
|
||||
<a href="/about/help" target="_self">
|
||||
здесь
|
||||
</a>
|
||||
.
|
||||
на ежемесячную поддержку проекта можно <a href="/about/help">здесь</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
@include font-size(2.2rem);
|
||||
|
||||
margin-bottom: 1.2rem;
|
||||
margin-top: 0.5rem !important;
|
||||
}
|
||||
|
||||
.topicAvatar {
|
||||
|
@ -104,3 +105,13 @@
|
|||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.topicCompact {
|
||||
.topicTitle {
|
||||
@include font-size(1.7rem);
|
||||
}
|
||||
}
|
||||
|
||||
.buttonCompact {
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ interface TopicProps {
|
|||
additionalClass?: string
|
||||
isTopicInRow?: boolean
|
||||
iconButton?: boolean
|
||||
showPublications?: boolean
|
||||
}
|
||||
|
||||
export const TopicCard = (props: TopicProps) => {
|
||||
|
@ -47,6 +48,7 @@ export const TopicCard = (props: TopicProps) => {
|
|||
class={styles.topic}
|
||||
classList={{
|
||||
row: !props.compact && !props.subscribeButtonBottom,
|
||||
[styles.topicCompact]: props.compact,
|
||||
[styles.topicInRow]: props.isTopicInRow
|
||||
}}
|
||||
>
|
||||
|
@ -58,7 +60,7 @@ export const TopicCard = (props: TopicProps) => {
|
|||
</Show>
|
||||
<Show when={props.topic.pic}>
|
||||
<div class={styles.topicAvatar}>
|
||||
<a href={props.topic.slug}>
|
||||
<a href={`/topic/${props.topic.slug}`}>
|
||||
<img src={props.topic.pic} alt={props.topic.title} />
|
||||
</a>
|
||||
</div>
|
||||
|
@ -75,7 +77,7 @@ export const TopicCard = (props: TopicProps) => {
|
|||
|
||||
<Show when={props.topic?.stat}>
|
||||
<div class={styles.topicDetails}>
|
||||
<Show when={!props.compact}>
|
||||
<Show when={props.showPublications}>
|
||||
<span class={styles.topicDetailsItem} classList={{ compact: props.compact }}>
|
||||
{props.topic.stat?.shouts +
|
||||
' ' +
|
||||
|
@ -85,6 +87,8 @@ export const TopicCard = (props: TopicProps) => {
|
|||
locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']
|
||||
)}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={!props.compact}>
|
||||
<span class={styles.topicDetailsItem} classList={{ compact: props.compact }}>
|
||||
{props.topic.stat?.authors +
|
||||
' ' +
|
||||
|
@ -116,15 +120,6 @@ export const TopicCard = (props: TopicProps) => {
|
|||
{/* </span>*/}
|
||||
{/*</Show>*/}
|
||||
</Show>
|
||||
|
||||
{/*
|
||||
<span class='topic-details__item'>
|
||||
{subscribers().toString() + ' ' + t('follower') + plural(
|
||||
subscribers(),
|
||||
locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']
|
||||
)}
|
||||
</span>
|
||||
*/}
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
@ -135,16 +130,26 @@ export const TopicCard = (props: TopicProps) => {
|
|||
<Show
|
||||
when={subscribed()}
|
||||
fallback={
|
||||
<button onClick={() => subscribe(true)} class="button--light button--subscribe-topic">
|
||||
<button
|
||||
onClick={() => subscribe(true)}
|
||||
class="button--light button--subscribe-topic"
|
||||
classList={{
|
||||
[styles.buttonCompact]: props.compact
|
||||
}}
|
||||
>
|
||||
<Show when={props.iconButton}>+</Show>
|
||||
|
||||
<Show when={!props.iconButton}>{t('Follow')}</Show>
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<button onClick={() => subscribe(false)} class="button--light button--subscribe-topic">
|
||||
<button
|
||||
onClick={() => subscribe(false)}
|
||||
class="button--light button--subscribe-topic"
|
||||
classList={{
|
||||
[styles.buttonCompact]: props.compact
|
||||
}}
|
||||
>
|
||||
<Show when={props.iconButton}>-</Show>
|
||||
|
||||
<Show when={!props.iconButton}>{t('Unfollow')}</Show>
|
||||
</button>
|
||||
</Show>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.topicHeader {
|
||||
@include font-size(1.7rem);
|
||||
|
||||
padding-top: 5.8rem;
|
||||
padding-top: 2.8rem;
|
||||
text-align: center;
|
||||
|
||||
h1 {
|
||||
|
|
|
@ -4,10 +4,11 @@ import { AuthorCard } from '../Author/Card'
|
|||
import { Icon } from '../_shared/Icon'
|
||||
import { t } from '../../utils/intl'
|
||||
import { useAuthorsStore, setAuthorsSort } from '../../stores/zine/authors'
|
||||
import { handleClientRouteLinkClick, useRouter } from '../../stores/router'
|
||||
import { useRouter } from '../../stores/router'
|
||||
import styles from '../../styles/AllTopics.module.scss'
|
||||
import { clsx } from 'clsx'
|
||||
import { useSession } from '../../context/session'
|
||||
import { locale } from '../../stores/ui'
|
||||
|
||||
type AllAuthorsPageSearchParams = {
|
||||
by: '' | 'name' | 'shouts' | 'rating'
|
||||
|
@ -35,18 +36,10 @@ 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] = []
|
||||
}
|
||||
|
||||
let letter = author.name.trim().split(' ').pop().at(0).toUpperCase()
|
||||
if (!/[А-я]/i.test(letter) && locale() === 'ru') letter = '@'
|
||||
if (!acc[letter]) acc[letter] = []
|
||||
acc[letter].push(author)
|
||||
|
||||
return acc
|
||||
}, {} as { [letter: string]: Author[] })
|
||||
})
|
||||
|
@ -70,19 +63,13 @@ export const AllAuthorsView = (props: Props) => {
|
|||
|
||||
<ul class={clsx(styles.viewSwitcher, 'view-switcher')}>
|
||||
<li classList={{ selected: searchParams().by === 'shouts' }}>
|
||||
<a href="/authors?by=shouts" onClick={handleClientRouteLinkClick}>
|
||||
{t('By shouts')}
|
||||
</a>
|
||||
<a href="/authors?by=shouts">{t('By shouts')}</a>
|
||||
</li>
|
||||
<li classList={{ selected: searchParams().by === 'rating' }}>
|
||||
<a href="/authors?by=rating" onClick={handleClientRouteLinkClick}>
|
||||
{t('By rating')}
|
||||
</a>
|
||||
<a href="/authors?by=rating">{t('By rating')}</a>
|
||||
</li>
|
||||
<li classList={{ selected: !searchParams().by || searchParams().by === 'name' }}>
|
||||
<a href="/authors" onClick={handleClientRouteLinkClick}>
|
||||
{t('By alphabet')}
|
||||
</a>
|
||||
<a href="/authors">{t('By alphabet')}</a>
|
||||
</li>
|
||||
<li class="view-switcher__search">
|
||||
<a href="/authors/search">
|
||||
|
@ -109,6 +96,7 @@ export const AllAuthorsView = (props: Props) => {
|
|||
subscribed={subscribed(author.slug)}
|
||||
noSocialButtons={true}
|
||||
isAuthorsList={true}
|
||||
truncateBio={true}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
|
@ -118,7 +106,7 @@ export const AllAuthorsView = (props: Props) => {
|
|||
<div class="row">
|
||||
<div class={clsx(styles.loadMoreContainer, 'col-12 col-md-10')}>
|
||||
<button class={clsx('button', styles.loadMoreButton)} onClick={showMore}>
|
||||
{t('More')}
|
||||
{t('Load more')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,11 +3,13 @@ import type { Topic } from '../../graphql/types.gen'
|
|||
import { Icon } from '../_shared/Icon'
|
||||
import { t } from '../../utils/intl'
|
||||
import { setTopicsSort, useTopicsStore } from '../../stores/zine/topics'
|
||||
import { handleClientRouteLinkClick, useRouter } from '../../stores/router'
|
||||
import { useRouter } from '../../stores/router'
|
||||
import { TopicCard } from '../Topic/Card'
|
||||
import styles from '../../styles/AllTopics.module.scss'
|
||||
import { clsx } from 'clsx'
|
||||
import { useSession } from '../../context/session'
|
||||
import { locale } from '../../stores/ui'
|
||||
import { translit } from '../../utils/ru2en'
|
||||
import styles from '../../styles/AllTopics.module.scss'
|
||||
|
||||
type AllTopicsPageSearchParams = {
|
||||
by: 'shouts' | 'authors' | 'title' | ''
|
||||
|
@ -37,13 +39,10 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
|
|||
|
||||
const byLetter = createMemo<{ [letter: string]: Topic[] }>(() => {
|
||||
return sortedTopics().reduce((acc, topic) => {
|
||||
const letter = topic.title[0].toUpperCase()
|
||||
if (!acc[letter]) {
|
||||
acc[letter] = []
|
||||
}
|
||||
|
||||
let letter = topic.title[0].toUpperCase()
|
||||
if (!/[А-я]/i.test(letter) && locale() === 'ru') letter = '#'
|
||||
if (!acc[letter]) acc[letter] = []
|
||||
acc[letter].push(topic)
|
||||
|
||||
return acc
|
||||
}, {} as { [letter: string]: Topic[] })
|
||||
})
|
||||
|
@ -57,70 +56,78 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
|
|||
const subscribed = (s) => Boolean(session()?.news?.topics && session()?.news?.topics?.includes(s || ''))
|
||||
|
||||
const showMore = () => setLimit((oldLimit) => oldLimit + PAGE_SIZE)
|
||||
let searchEl: HTMLInputElement
|
||||
const [searchResults, setSearchResults] = createSignal<Topic[]>([])
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
const searchTopics = () => {
|
||||
/* very stupid search algorithm with no deps */
|
||||
let q = searchEl.value.toLowerCase()
|
||||
if (q.length > 0) {
|
||||
console.debug(q)
|
||||
setSearchResults([])
|
||||
|
||||
if (locale() === 'ru') q = translit(q, 'ru')
|
||||
const ttt: Topic[] = []
|
||||
sortedTopics().forEach((topic) => {
|
||||
let flag = false
|
||||
topic.slug.split('-').forEach((w) => {
|
||||
if (w.startsWith(q)) flag = true
|
||||
})
|
||||
|
||||
if (!flag) {
|
||||
let wrds: string = topic.title.toLowerCase()
|
||||
if (locale() === 'ru') wrds = translit(wrds, 'ru')
|
||||
wrds.split(' ').forEach((w: string) => {
|
||||
if (w.startsWith(q)) flag = true
|
||||
})
|
||||
}
|
||||
|
||||
if (flag && !ttt.includes(topic)) ttt.push(topic)
|
||||
})
|
||||
|
||||
setSearchResults((sr: Topic[]) => [...sr, ...ttt])
|
||||
changeSearchParam('by', '')
|
||||
}
|
||||
}
|
||||
|
||||
const AllTopicsHead = () => (
|
||||
<div class="row">
|
||||
<div class={clsx(styles.pageHeader, 'col-lg-10 col-xl-9')}>
|
||||
<h1>{t('Topics')}</h1>
|
||||
<p>{t('Subscribe what you like to tune your personal feed')}</p>
|
||||
|
||||
<ul class={clsx(styles.viewSwitcher, 'view-switcher')}>
|
||||
<li classList={{ selected: searchParams().by === 'shouts' }}>
|
||||
<a href="/topics?by=shouts">{t('By shouts')}</a>
|
||||
</li>
|
||||
<li classList={{ selected: searchParams().by === 'authors' }}>
|
||||
<a href="/topics?by=authors">{t('By authors')}</a>
|
||||
</li>
|
||||
<li classList={{ selected: searchParams().by === 'title' }}>
|
||||
<a href="/topics?by=title">{t('By alphabet')}</a>
|
||||
</li>
|
||||
<li class="search-switcher">
|
||||
<Icon name="search" />
|
||||
<input
|
||||
class="search-input"
|
||||
ref={searchEl}
|
||||
onChange={searchTopics}
|
||||
onInput={searchTopics}
|
||||
onFocus={() => (searchEl.innerHTML = '')}
|
||||
placeholder={t('Search')}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div class={clsx(styles.allTopicsPage, 'container')}>
|
||||
<Show when={sortedTopics().length > 0}>
|
||||
<div class="shift-content">
|
||||
<div class="row">
|
||||
<div class={clsx(styles.pageHeader, 'col-lg-10 col-xl-9')}>
|
||||
<h1>{t('Topics')}</h1>
|
||||
<p>{t('Subscribe what you like to tune your personal feed')}</p>
|
||||
<AllTopicsHead />
|
||||
|
||||
<ul class={clsx(styles.viewSwitcher, 'view-switcher')}>
|
||||
<li classList={{ selected: searchParams().by === 'shouts' || !searchParams().by }}>
|
||||
<a href="/topics?by=shouts" onClick={handleClientRouteLinkClick}>
|
||||
{t('By shouts')}
|
||||
</a>
|
||||
</li>
|
||||
<li classList={{ selected: searchParams().by === 'authors' }}>
|
||||
<a href="/topics?by=authors" onClick={handleClientRouteLinkClick}>
|
||||
{t('By authors')}
|
||||
</a>
|
||||
</li>
|
||||
<li classList={{ selected: searchParams().by === 'title' }}>
|
||||
<a
|
||||
href="/topics?by=title"
|
||||
onClick={(ev) => {
|
||||
// just an example
|
||||
ev.preventDefault()
|
||||
changeSearchParam('by', 'title')
|
||||
}}
|
||||
>
|
||||
{t('By alphabet')}
|
||||
</a>
|
||||
</li>
|
||||
<li class="view-switcher__search">
|
||||
<a href="/topic/search">
|
||||
<Icon name="search" />
|
||||
{t('Search topic')}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show
|
||||
when={searchParams().by === 'title'}
|
||||
fallback={() => (
|
||||
<>
|
||||
<For each={sortedTopics().slice(0, limit())}>
|
||||
{(topic) => (
|
||||
<TopicCard topic={topic} compact={false} subscribed={subscribed(topic.slug)} />
|
||||
)}
|
||||
</For>
|
||||
<Show when={sortedTopics().length > limit()}>
|
||||
<div class="row">
|
||||
<div class={clsx(styles.loadMoreContainer, 'col-12 col-md-10')}>
|
||||
<button class={clsx('button', styles.loadMoreButton)} onClick={showMore}>
|
||||
{t('More')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</>
|
||||
)}
|
||||
>
|
||||
<div class="shift-content">
|
||||
<Show when={sortedTopics().length > 0 || searchResults().length > 0}>
|
||||
<Show when={searchParams().by === 'title'}>
|
||||
<For each={sortedKeys()}>
|
||||
{(letter) => (
|
||||
<div class={clsx(styles.group, 'group')}>
|
||||
|
@ -146,8 +153,57 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
|
|||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={searchResults().length > 1}>
|
||||
<For each={searchResults().slice(0, limit())}>
|
||||
{(topic) => (
|
||||
<TopicCard
|
||||
topic={topic}
|
||||
compact={false}
|
||||
subscribed={subscribed(topic.slug)}
|
||||
showPublications={true}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
|
||||
<Show when={searchParams().by === 'authors'}>
|
||||
<For each={sortedTopics().slice(0, limit())}>
|
||||
{(topic) => (
|
||||
<TopicCard
|
||||
topic={topic}
|
||||
compact={false}
|
||||
subscribed={subscribed(topic.slug)}
|
||||
showPublications={true}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
|
||||
<Show when={searchParams().by === 'shouts'}>
|
||||
<For each={sortedTopics().slice(0, limit())}>
|
||||
{(topic) => (
|
||||
<TopicCard
|
||||
topic={topic}
|
||||
compact={false}
|
||||
subscribed={subscribed(topic.slug)}
|
||||
showPublications={true}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
|
||||
<Show when={sortedTopics().length > limit()}>
|
||||
<div class="row">
|
||||
<div class={clsx(styles.loadMoreContainer, 'col-12 col-md-10')}>
|
||||
<button class={clsx('button', styles.loadMoreButton)} onClick={showMore}>
|
||||
{t('Load more')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Row3 } from '../Feed/Row3'
|
|||
import { AuthorFull } from '../Author/Full'
|
||||
import { t } from '../../utils/intl'
|
||||
import { useAuthorsStore } from '../../stores/zine/authors'
|
||||
import { loadShoutsBy, useArticlesStore } from '../../stores/zine/articles'
|
||||
import { loadShouts, useArticlesStore } from '../../stores/zine/articles'
|
||||
|
||||
import { useTopicsStore } from '../../stores/zine/topics'
|
||||
import { useRouter } from '../../stores/router'
|
||||
|
@ -18,7 +18,7 @@ type AuthorProps = {
|
|||
shouts: Shout[]
|
||||
author: Author
|
||||
authorSlug: string
|
||||
// FIXME author topics fro server
|
||||
// FIXME author topics from server
|
||||
// topics: Topic[]
|
||||
}
|
||||
|
||||
|
@ -42,8 +42,8 @@ export const AuthorView = (props: AuthorProps) => {
|
|||
|
||||
const loadMore = async () => {
|
||||
saveScrollPosition()
|
||||
const { hasMore } = await loadShoutsBy({
|
||||
by: { author: author().slug },
|
||||
const { hasMore } = await loadShouts({
|
||||
filters: { author: author().slug },
|
||||
limit: LOAD_MORE_PAGE_SIZE,
|
||||
offset: sortedArticles().length
|
||||
})
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Editor } from '../EditorNew/Editor'
|
||||
import { ClientContainer } from '../_shared/ClientContainer'
|
||||
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
|
||||
|
||||
export const CreateView = () => {
|
||||
return (
|
||||
<ClientContainer>
|
||||
<ShowOnlyOnClient>
|
||||
<Editor />
|
||||
</ClientContainer>
|
||||
</ShowOnlyOnClient>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import { AuthorCard } from '../Author/Card'
|
|||
import { t } from '../../utils/intl'
|
||||
import { FeedSidebar } from '../Feed/Sidebar'
|
||||
import CommentCard from '../Article/Comment'
|
||||
import { useArticlesStore } from '../../stores/zine/articles'
|
||||
import { loadShouts, useArticlesStore } from '../../stores/zine/articles'
|
||||
import { useReactionsStore } from '../../stores/zine/reactions'
|
||||
import { useAuthorsStore } from '../../stores/zine/authors'
|
||||
import { useTopicsStore } from '../../stores/zine/topics'
|
||||
|
@ -28,7 +28,7 @@ export const FEED_PAGE_SIZE = 20
|
|||
|
||||
export const FeedView = () => {
|
||||
// state
|
||||
const { sortedArticles, loadShoutsBy } = useArticlesStore()
|
||||
const { sortedArticles } = useArticlesStore()
|
||||
const { sortedReactions: topComments, loadReactionsBy } = useReactionsStore({})
|
||||
const { sortedAuthors } = useAuthorsStore()
|
||||
const { topTopics } = useTopicsStore()
|
||||
|
@ -37,8 +37,8 @@ export const FeedView = () => {
|
|||
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
|
||||
|
||||
const loadMore = async () => {
|
||||
const { hasMore } = await loadShoutsBy({
|
||||
by: { visibility: 'community' },
|
||||
const { hasMore } = await loadShouts({
|
||||
filters: { visibility: 'community' },
|
||||
limit: FEED_PAGE_SIZE,
|
||||
offset: sortedArticles().length
|
||||
})
|
||||
|
@ -57,7 +57,7 @@ export const FeedView = () => {
|
|||
|
||||
// load recent editing shouts ( visibility = authors )
|
||||
const userslug = session().user.slug
|
||||
await loadShoutsBy({ by: { author: userslug, visibility: 'authors' }, limit: 15, offset: 0 })
|
||||
await loadShouts({ filters: { author: userslug, visibility: 'authors' }, limit: 15, offset: 0 })
|
||||
const collaborativeShouts = sortedArticles().filter((s: Shout, n: number, arr: Shout[]) => {
|
||||
if (s.visibility !== 'authors') {
|
||||
arr.splice(n, 1)
|
||||
|
@ -84,13 +84,13 @@ export const FeedView = () => {
|
|||
</li>
|
||||
</Show>
|
||||
<li>
|
||||
<a href="?by=views">{t('Most read')}</a>
|
||||
<a href="/feed/?by=views">{t('Most read')}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="?by=rating">{t('Top rated')}</a>
|
||||
<a href="/feed/?by=rating">{t('Top rated')}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="?by=comments">{t('Most commented')}</a>
|
||||
<a href="/feed/?by=comments">{t('Most commented')}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
@ -101,7 +101,7 @@ export const FeedView = () => {
|
|||
|
||||
<div class={stylesBeside.besideColumnTitle}>
|
||||
<h4>{t('Popular authors')}</h4>
|
||||
<a href="/user/list">
|
||||
<a href="/authors">
|
||||
{t('All authors')}
|
||||
<Icon name="arrow-right" />
|
||||
</a>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import '../../styles/FeedSettings.scss'
|
||||
import { t } from '../../utils/intl'
|
||||
import { handleClientRouteLinkClick } from '../../stores/router'
|
||||
|
||||
// type FeedSettingsSearchParams = {
|
||||
// by: '' | 'topics' | 'authors' | 'reacted'
|
||||
|
@ -13,9 +12,7 @@ export const FeedSettingsView = (_props) => {
|
|||
|
||||
<ul class="view-switcher">
|
||||
<li class="selected">
|
||||
<a href="?by=topics" onClick={handleClientRouteLinkClick}>
|
||||
{t('topics')}
|
||||
</a>
|
||||
<a href="?by=topics">{t('topics')}</a>
|
||||
</li>
|
||||
{/*<li>
|
||||
<a href="?by=collections" onClick={() => setBy('collections')}>
|
||||
|
@ -23,14 +20,10 @@ export const FeedSettingsView = (_props) => {
|
|||
</a>
|
||||
</li>*/}
|
||||
<li>
|
||||
<a href="?by=authors" onClick={handleClientRouteLinkClick}>
|
||||
{t('authors')}
|
||||
</a>
|
||||
<a href="?by=authors">{t('authors')}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="?by=reacted" onClick={handleClientRouteLinkClick}>
|
||||
{t('reactions')}
|
||||
</a>
|
||||
<a href="?by=reacted">{t('reactions')}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
@ -11,10 +11,9 @@ import RowShort from '../Feed/RowShort'
|
|||
import Slider from '../Feed/Slider'
|
||||
import Group from '../Feed/Group'
|
||||
import type { Shout, Topic } from '../../graphql/types.gen'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
import { t } from '../../utils/intl'
|
||||
import { useTopicsStore } from '../../stores/zine/topics'
|
||||
import { loadShoutsBy, useArticlesStore } from '../../stores/zine/articles'
|
||||
import { loadShouts, useArticlesStore } from '../../stores/zine/articles'
|
||||
import { useTopAuthorsStore } from '../../stores/zine/topAuthors'
|
||||
import { locale } from '../../stores/ui'
|
||||
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
|
||||
|
@ -48,8 +47,7 @@ export const HomeView = (props: HomeProps) => {
|
|||
|
||||
onMount(async () => {
|
||||
if (sortedArticles().length < PRERENDERED_ARTICLES_COUNT + CLIENT_LOAD_ARTICLES_COUNT) {
|
||||
const { hasMore } = await loadShoutsBy({
|
||||
by: {},
|
||||
const { hasMore } = await loadShouts({
|
||||
limit: CLIENT_LOAD_ARTICLES_COUNT,
|
||||
offset: sortedArticles().length
|
||||
})
|
||||
|
@ -69,14 +67,7 @@ export const HomeView = (props: HomeProps) => {
|
|||
|
||||
return (
|
||||
<Show when={Boolean(selectedRandomLayout)}>
|
||||
<Group
|
||||
articles={articlesByLayout()[selectedRandomLayout]}
|
||||
header={
|
||||
<div class="layout-icon">
|
||||
<Icon name={selectedRandomLayout} />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Group articles={articlesByLayout()[selectedRandomLayout]} header={''} />
|
||||
</Show>
|
||||
)
|
||||
})
|
||||
|
@ -84,8 +75,8 @@ export const HomeView = (props: HomeProps) => {
|
|||
const loadMore = async () => {
|
||||
saveScrollPosition()
|
||||
|
||||
const { hasMore } = await loadShoutsBy({
|
||||
by: { visibility: 'public' },
|
||||
const { hasMore } = await loadShouts({
|
||||
filters: { visibility: 'public' },
|
||||
limit: LOAD_MORE_PAGE_SIZE,
|
||||
offset: sortedArticles().length
|
||||
})
|
||||
|
|
|
@ -3,8 +3,9 @@ import '../../styles/Search.scss'
|
|||
import type { Shout } from '../../graphql/types.gen'
|
||||
import { ArticleCard } from '../Feed/Card'
|
||||
import { t } from '../../utils/intl'
|
||||
import { useArticlesStore, loadShoutsBy } from '../../stores/zine/articles'
|
||||
import { handleClientRouteLinkClick, useRouter } from '../../stores/router'
|
||||
import { loadShouts, useArticlesStore } from '../../stores/zine/articles'
|
||||
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
|
||||
import { useRouter } from '../../stores/router'
|
||||
|
||||
type SearchPageSearchParams = {
|
||||
by: '' | 'relevance' | 'rating'
|
||||
|
@ -15,31 +16,49 @@ type Props = {
|
|||
results: Shout[]
|
||||
}
|
||||
|
||||
const LOAD_MORE_PAGE_SIZE = 50
|
||||
|
||||
export const SearchView = (props: Props) => {
|
||||
const { sortedArticles } = useArticlesStore({ shouts: props.results })
|
||||
const [getQuery, setQuery] = createSignal(props.query)
|
||||
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
|
||||
const [query, setQuery] = createSignal(props.query)
|
||||
const [offset, setOffset] = createSignal(0)
|
||||
|
||||
const { searchParams } = useRouter<SearchPageSearchParams>()
|
||||
|
||||
const handleQueryChange = (ev) => {
|
||||
setQuery(ev.target.value)
|
||||
const { searchParams, handleClientRouteLinkClick } = useRouter<SearchPageSearchParams>()
|
||||
let searchEl: HTMLInputElement
|
||||
const handleQueryChange = (_ev) => {
|
||||
setQuery(searchEl.value)
|
||||
}
|
||||
|
||||
const handleSubmit = (_ev) => {
|
||||
// TODO page
|
||||
// TODO sort
|
||||
loadShoutsBy({ by: { title: getQuery(), body: getQuery() }, limit: 50 })
|
||||
const loadMore = async () => {
|
||||
saveScrollPosition()
|
||||
const { hasMore } = await loadShouts({
|
||||
filters: {
|
||||
title: query(),
|
||||
body: query()
|
||||
},
|
||||
offset: offset(),
|
||||
limit: LOAD_MORE_PAGE_SIZE
|
||||
})
|
||||
setIsLoadMoreButtonVisible(hasMore)
|
||||
setOffset(offset() + LOAD_MORE_PAGE_SIZE)
|
||||
restoreScrollPosition()
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="search-page wide-container">
|
||||
<form action="/search" class="search-form row">
|
||||
<div class="col-sm-9">
|
||||
{/*FIXME t*/}
|
||||
<input type="search" name="q" onChange={handleQueryChange} placeholder="Введите текст..." />
|
||||
<input
|
||||
type="search"
|
||||
name="q"
|
||||
ref={searchEl}
|
||||
onInput={handleQueryChange}
|
||||
placeholder={t('Enter text') + '...'}
|
||||
/>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<button class="button" type="submit" onClick={handleSubmit}>
|
||||
<button class="button" type="submit" onClick={loadMore}>
|
||||
{t('Search')}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -87,9 +106,13 @@ export const SearchView = (props: Props) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<h3>{t('Topics')}</h3>
|
||||
|
||||
<h3>{t('Authors')}</h3>
|
||||
<Show when={isLoadMoreButtonVisible()}>
|
||||
<p class="load-more-container">
|
||||
<button class="button" onClick={loadMore}>
|
||||
{t('Load more')}
|
||||
</button>
|
||||
</p>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -8,7 +8,7 @@ import { FullTopic } from '../Topic/Full'
|
|||
import { t } from '../../utils/intl'
|
||||
import { useRouter } from '../../stores/router'
|
||||
import { useTopicsStore } from '../../stores/zine/topics'
|
||||
import { loadShoutsBy, useArticlesStore } from '../../stores/zine/articles'
|
||||
import { loadShouts, useArticlesStore } from '../../stores/zine/articles'
|
||||
import { useAuthorsStore } from '../../stores/zine/authors'
|
||||
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
|
||||
import { splitToPages } from '../../utils/splitToPages'
|
||||
|
@ -44,8 +44,8 @@ export const TopicView = (props: TopicProps) => {
|
|||
const loadMore = async () => {
|
||||
saveScrollPosition()
|
||||
|
||||
const { hasMore } = await loadShoutsBy({
|
||||
by: { topic: topic().slug },
|
||||
const { hasMore } = await loadShouts({
|
||||
filters: { topic: topic().slug },
|
||||
limit: LOAD_MORE_PAGE_SIZE,
|
||||
offset: sortedArticles().length
|
||||
})
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import type { JSX } from 'solid-js'
|
||||
import { createSignal, onMount, Show } from 'solid-js'
|
||||
|
||||
// show children only on client side
|
||||
// usage of isServer causing hydration errors
|
||||
export const ClientContainer = (props: { children: JSX.Element }) => {
|
||||
const [isMounted, setIsMounted] = createSignal(false)
|
||||
|
||||
onMount(() => setIsMounted(true))
|
||||
|
||||
return <Show when={isMounted()}>{props.children}</Show>
|
||||
}
|
|
@ -4,12 +4,14 @@ import { Footer } from '../Discours/Footer'
|
|||
|
||||
import '../../styles/app.scss'
|
||||
import { Show } from 'solid-js'
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
type PageWrapProps = {
|
||||
headerTitle?: string
|
||||
children: JSX.Element
|
||||
isHeaderFixed?: boolean
|
||||
hideFooter?: boolean
|
||||
class?: string
|
||||
}
|
||||
|
||||
export const PageWrap = (props: PageWrapProps) => {
|
||||
|
@ -18,7 +20,10 @@ export const PageWrap = (props: PageWrapProps) => {
|
|||
return (
|
||||
<>
|
||||
<Header title={props.headerTitle} isHeaderFixed={isHeaderFixed} />
|
||||
<main class="main-content" classList={{ 'main-content--no-padding': !isHeaderFixed }}>
|
||||
<main
|
||||
class={clsx('main-content', props.class)}
|
||||
classList={{ 'main-content--no-padding': !isHeaderFixed }}
|
||||
>
|
||||
{props.children}
|
||||
</main>
|
||||
<Show when={props.hideFooter !== true}>
|
||||
|
|
12
src/components/_shared/ShowOnlyOnClient.tsx
Normal file
12
src/components/_shared/ShowOnlyOnClient.tsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
import type { JSX } from 'solid-js'
|
||||
import { createSignal, onMount, Show } from 'solid-js'
|
||||
|
||||
const [isClient, setIsClient] = createSignal(false)
|
||||
|
||||
// show children only on client side
|
||||
// usage of isServer causing hydration errors
|
||||
export const ShowOnlyOnClient = (props: { children: JSX.Element }) => {
|
||||
onMount(() => setIsClient(true))
|
||||
|
||||
return <Show when={isClient()}>{props.children}</Show>
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import { gql } from '@urql/core'
|
||||
|
||||
export default gql`
|
||||
query LoadShoutsByQuery($by: ShoutsBy, $limit: Int!, $offset: Int!) {
|
||||
loadShoutsBy(by: $by, limit: $limit, offset: $offset) {
|
||||
query LoadShoutsQuery($options: LoadShoutsOptions) {
|
||||
loadShouts(options: $options) {
|
||||
_id: slug
|
||||
title
|
||||
subtitle
|
||||
|
|
42
src/graphql/query/articles-load.ts
Normal file
42
src/graphql/query/articles-load.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { gql } from '@urql/core'
|
||||
|
||||
export default gql`
|
||||
query LoadShoutQuery($slug: String!) {
|
||||
loadShout(slug: $slug) {
|
||||
_id: slug
|
||||
title
|
||||
subtitle
|
||||
slug
|
||||
layout
|
||||
cover
|
||||
body
|
||||
# community
|
||||
mainTopic
|
||||
topics {
|
||||
title
|
||||
body
|
||||
slug
|
||||
stat {
|
||||
_id: shouts
|
||||
shouts
|
||||
authors
|
||||
followers
|
||||
}
|
||||
}
|
||||
authors {
|
||||
_id: slug
|
||||
name
|
||||
slug
|
||||
userpic
|
||||
}
|
||||
createdAt
|
||||
publishedAt
|
||||
stat {
|
||||
_id: viewed
|
||||
viewed
|
||||
reacted
|
||||
rating
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
|
@ -12,11 +12,11 @@ export default gql`
|
|||
links
|
||||
createdAt
|
||||
lastSeen
|
||||
ratings {
|
||||
_id: rater
|
||||
rater
|
||||
value
|
||||
}
|
||||
# ratings {
|
||||
# _id: rater
|
||||
# rater
|
||||
# value
|
||||
# }
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -8,15 +8,15 @@ export default gql`
|
|||
name
|
||||
bio
|
||||
userpic
|
||||
communities
|
||||
# communities
|
||||
links
|
||||
createdAt
|
||||
# createdAt
|
||||
lastSeen
|
||||
ratings {
|
||||
_id: rater
|
||||
rater
|
||||
value
|
||||
}
|
||||
# ratings {
|
||||
# _id: rater
|
||||
# rater
|
||||
# value
|
||||
# }
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { gql } from '@urql/core'
|
||||
|
||||
export default gql`
|
||||
query LoadReactionsByQuery($by: ReactionsBy, $limit: Int!, $offset: Int!) {
|
||||
query LoadReactionsByQuery($by: ReactionBy!, $limit: Int!, $offset: Int!) {
|
||||
loadReactionsBy(by: $by, limit: $limit, offset: $offset) {
|
||||
id
|
||||
createdBy {
|
||||
|
|
|
@ -80,6 +80,14 @@ export type ChatMember = {
|
|||
userpic?: Maybe<Scalars['String']>
|
||||
}
|
||||
|
||||
export type ChatUser = {
|
||||
id: Scalars['Int']
|
||||
lastSeen?: Maybe<Scalars['DateTime']>
|
||||
name: Scalars['String']
|
||||
slug: Scalars['String']
|
||||
userpic?: Maybe<Scalars['String']>
|
||||
}
|
||||
|
||||
export type Collab = {
|
||||
authors: Array<Maybe<Scalars['String']>>
|
||||
body?: Maybe<Scalars['String']>
|
||||
|
@ -116,6 +124,25 @@ export enum FollowingEntity {
|
|||
Topic = 'TOPIC'
|
||||
}
|
||||
|
||||
export type LoadShoutsFilters = {
|
||||
author?: InputMaybe<Scalars['String']>
|
||||
body?: InputMaybe<Scalars['String']>
|
||||
days?: InputMaybe<Scalars['Int']>
|
||||
layout?: InputMaybe<Scalars['String']>
|
||||
reacted?: InputMaybe<Scalars['Boolean']>
|
||||
title?: InputMaybe<Scalars['String']>
|
||||
topic?: InputMaybe<Scalars['String']>
|
||||
visibility?: InputMaybe<Scalars['String']>
|
||||
}
|
||||
|
||||
export type LoadShoutsOptions = {
|
||||
filters?: InputMaybe<LoadShoutsFilters>
|
||||
limit: Scalars['Int']
|
||||
offset?: InputMaybe<Scalars['Int']>
|
||||
order_by?: InputMaybe<Scalars['String']>
|
||||
order_by_desc?: InputMaybe<Scalars['Boolean']>
|
||||
}
|
||||
|
||||
export type Message = {
|
||||
author: Scalars['String']
|
||||
body: Scalars['String']
|
||||
|
@ -183,7 +210,7 @@ export type MutationCreateChatArgs = {
|
|||
|
||||
export type MutationCreateMessageArgs = {
|
||||
body: Scalars['String']
|
||||
chatId: Scalars['String']
|
||||
chat: Scalars['String']
|
||||
replyTo?: InputMaybe<Scalars['String']>
|
||||
}
|
||||
|
||||
|
@ -317,15 +344,17 @@ export type ProfileInput = {
|
|||
|
||||
export type Query = {
|
||||
authorsAll: Array<Maybe<Author>>
|
||||
getAuthor: User
|
||||
chatUsersAll: Array<Maybe<ChatUser>>
|
||||
getAuthor?: Maybe<User>
|
||||
getCollabs: Array<Maybe<Collab>>
|
||||
getTopic: Topic
|
||||
getTopic?: Maybe<Topic>
|
||||
isEmailUsed: Scalars['Boolean']
|
||||
loadAuthorsBy: Array<Maybe<Author>>
|
||||
loadChats: Result
|
||||
loadMessagesBy: Result
|
||||
loadReactionsBy: Array<Maybe<Reaction>>
|
||||
loadShoutsBy: Array<Maybe<Shout>>
|
||||
loadShout?: Maybe<Shout>
|
||||
loadShouts: Array<Maybe<Shout>>
|
||||
markdownBody: Scalars['String']
|
||||
searchUsers: Result
|
||||
signIn: AuthResult
|
||||
|
@ -352,8 +381,8 @@ export type QueryIsEmailUsedArgs = {
|
|||
}
|
||||
|
||||
export type QueryLoadAuthorsByArgs = {
|
||||
limit?: InputMaybe<Scalars['Int']>
|
||||
by?: InputMaybe<AuthorsBy>
|
||||
limit?: InputMaybe<Scalars['Int']>
|
||||
offset?: InputMaybe<Scalars['Int']>
|
||||
}
|
||||
|
||||
|
@ -363,21 +392,23 @@ export type QueryLoadChatsArgs = {
|
|||
}
|
||||
|
||||
export type QueryLoadMessagesByArgs = {
|
||||
limit?: InputMaybe<Scalars['Int']>
|
||||
by: MessagesBy
|
||||
limit?: InputMaybe<Scalars['Int']>
|
||||
offset?: InputMaybe<Scalars['Int']>
|
||||
}
|
||||
|
||||
export type QueryLoadReactionsByArgs = {
|
||||
offset?: InputMaybe<Scalars['Int']>
|
||||
by: ReactionBy
|
||||
limit?: InputMaybe<Scalars['Int']>
|
||||
offset?: InputMaybe<Scalars['Int']>
|
||||
}
|
||||
|
||||
export type QueryLoadShoutsByArgs = {
|
||||
limit?: InputMaybe<Scalars['Int']>
|
||||
by?: InputMaybe<ShoutsBy>
|
||||
offset?: InputMaybe<Scalars['Int']>
|
||||
export type QueryLoadShoutArgs = {
|
||||
slug: Scalars['String']
|
||||
}
|
||||
|
||||
export type QueryLoadShoutsArgs = {
|
||||
options?: InputMaybe<LoadShoutsOptions>
|
||||
}
|
||||
|
||||
export type QueryMarkdownBodyArgs = {
|
||||
|
@ -563,12 +594,12 @@ export type ShoutInput = {
|
|||
visibleForUsers?: InputMaybe<Array<InputMaybe<Scalars['String']>>>
|
||||
}
|
||||
|
||||
export type ShoutsBy = {
|
||||
export type ShoutsFilterBy = {
|
||||
author?: InputMaybe<Scalars['String']>
|
||||
authors?: InputMaybe<Array<InputMaybe<Scalars['String']>>>
|
||||
body?: InputMaybe<Scalars['String']>
|
||||
days?: InputMaybe<Scalars['Int']>
|
||||
layout?: InputMaybe<Scalars['String']>
|
||||
order?: InputMaybe<Scalars['String']>
|
||||
slug?: InputMaybe<Scalars['String']>
|
||||
stat?: InputMaybe<Scalars['String']>
|
||||
title?: InputMaybe<Scalars['String']>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"All": "Все",
|
||||
"All posts": "Все публикации",
|
||||
"All topics": "Все темы",
|
||||
"All authors": "Все авторы",
|
||||
"Authors": "Авторы",
|
||||
"Back to mainpage": "Вернуться на главную",
|
||||
"Become an author": "Стать автором",
|
||||
|
@ -175,5 +176,6 @@
|
|||
"Video": "Видео",
|
||||
"Literature": "Литература",
|
||||
"We can't find you, check email or": "Не можем вас найти, проверьте адрес электронной почты или",
|
||||
"register": "зарегистрируйтесь"
|
||||
"register": "зарегистрируйтесь",
|
||||
"Enter text": "Введите текст"
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ if (slug.endsWith('.map')) {
|
|||
return Astro.redirect('/404')
|
||||
}
|
||||
|
||||
const article = await apiClient.loadShoutsBy({ by: { slug }, limit: 1})
|
||||
const article = await apiClient.getShout(slug)
|
||||
if (!article) {
|
||||
return Astro.redirect('/404')
|
||||
}
|
||||
|
@ -21,5 +21,5 @@ Astro.response.headers.set('Cache-Control', 's-maxage=1, stale-while-revalidate'
|
|||
---
|
||||
|
||||
<Prerendered>
|
||||
<Root article={article.at(0)} client:load />
|
||||
<Root article={article} client:load />
|
||||
</Prerendered>
|
||||
|
|
|
@ -6,8 +6,8 @@ import { initRouter } from '../../../stores/router'
|
|||
import { PRERENDERED_ARTICLES_COUNT } from '../../../components/Views/Author'
|
||||
|
||||
const slug = Astro.params.slug.toString()
|
||||
const shouts = await apiClient.loadShoutsBy({ by: { authors: [slug] } , limit: PRERENDERED_ARTICLES_COUNT })
|
||||
const author = await apiClient.loadAuthorsBy({ by: { slug } })
|
||||
const shouts = await apiClient.getShouts({ filters: { author: slug }, limit: PRERENDERED_ARTICLES_COUNT })
|
||||
const author = await apiClient.getAuthorsBy({ by: { slug } })
|
||||
|
||||
const { pathname, search } = Astro.url
|
||||
initRouter(pathname, search)
|
||||
|
|
|
@ -4,13 +4,12 @@ import Prerendered from '../../main.astro'
|
|||
import { apiClient } from '../../utils/apiClient'
|
||||
import { initRouter } from '../../stores/router'
|
||||
import type { LayoutType } from '../../stores/zine/layouts'
|
||||
import { Layout } from '../../components/EditorExample/components/Layout'
|
||||
|
||||
const layout = (Astro.params.layout?.toString() || 'article') as LayoutType
|
||||
if (!layout || layout.endsWith('.map')) {
|
||||
return Astro.redirect('/404')
|
||||
}
|
||||
const shouts = await apiClient.loadShoutsBy({ by: { layout } })
|
||||
const shouts = await apiClient.getShouts({ filters: { layout }, limit: 50 })
|
||||
const { pathname, search } = Astro.url
|
||||
initRouter(pathname, search)
|
||||
---
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
---
|
||||
import Prerendered from '../main.astro'
|
||||
import { Root } from '../components/Root'
|
||||
import { apiClient } from '../utils/apiClient'
|
||||
import { initRouter } from '../stores/router'
|
||||
|
||||
const { pathname, search } = Astro.url
|
||||
|
|
|
@ -6,8 +6,8 @@ import { initRouter } from '../stores/router'
|
|||
import { PRERENDERED_ARTICLES_COUNT } from '../components/Views/Home'
|
||||
|
||||
const randomTopics = await apiClient.getRandomTopics({ amount: 12 })
|
||||
const articles = await apiClient.loadShoutsBy(
|
||||
{ by: { visibility: "public" }, limit: PRERENDERED_ARTICLES_COUNT, offset: 0 })
|
||||
const articles = await apiClient.getShouts(
|
||||
{ filters: { visibility: "public" }, limit: PRERENDERED_ARTICLES_COUNT })
|
||||
|
||||
const { pathname, search } = Astro.url
|
||||
initRouter(pathname, search)
|
||||
|
|
|
@ -6,7 +6,7 @@ import { initRouter } from '../stores/router'
|
|||
|
||||
const params: URLSearchParams = Astro.url.searchParams
|
||||
const q = params.get('q')
|
||||
const searchResults = await apiClient.loadShoutsBy({ by: { title: q, body: q }, limit: 50 })
|
||||
const searchResults = await apiClient.getShouts({ filters: { title: q, body: q }, limit: 50 })
|
||||
|
||||
const { pathname, search } = Astro.url
|
||||
initRouter(pathname, search)
|
||||
|
|
|
@ -5,7 +5,7 @@ import { apiClient } from '../../utils/apiClient'
|
|||
import { PRERENDERED_ARTICLES_COUNT } from '../../components/Views/Topic'
|
||||
|
||||
const slug = Astro.params.slug?.toString() || ''
|
||||
const shouts = await apiClient.loadShoutsBy({ by: { topics: [slug] }, limit: PRERENDERED_ARTICLES_COUNT })
|
||||
const shouts = await apiClient.getShouts({ filters: { topic: slug }, limit: PRERENDERED_ARTICLES_COUNT })
|
||||
const topic = await apiClient.getTopic({ slug })
|
||||
|
||||
import { initRouter } from '../../stores/router'
|
||||
|
|
|
@ -63,7 +63,7 @@ const routerStore = createRouter<Routes>(
|
|||
|
||||
export const router = routerStore
|
||||
|
||||
export const handleClientRouteLinkClick = (event) => {
|
||||
const handleClientRouteLinkClick = (event) => {
|
||||
const link = event.target.closest('a')
|
||||
if (
|
||||
link &&
|
||||
|
@ -96,6 +96,10 @@ export const initRouter = (pathname: string, search: string) => {
|
|||
routerStore.open(pathname)
|
||||
const params = Object.fromEntries(new URLSearchParams(search))
|
||||
searchParamsStore.open(params)
|
||||
|
||||
if (!isServer) {
|
||||
document.addEventListener('click', handleClientRouteLinkClick)
|
||||
}
|
||||
}
|
||||
|
||||
if (!isServer) {
|
||||
|
@ -125,6 +129,7 @@ export const useRouter = <TSearchParams extends Record<string, string> = Record<
|
|||
return {
|
||||
page,
|
||||
searchParams,
|
||||
changeSearchParam
|
||||
changeSearchParam,
|
||||
handleClientRouteLinkClick
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { Author, Shout, ShoutInput, ShoutsBy, Topic } from '../../graphql/types.gen'
|
||||
import type { Author, Shout, ShoutInput, Topic, LoadShoutsOptions } from '../../graphql/types.gen'
|
||||
import { apiClient } from '../../utils/apiClient'
|
||||
import { addAuthorsByTopic } from './authors'
|
||||
import { addTopicsByAuthor } from './topics'
|
||||
|
@ -123,22 +123,18 @@ const addSortedArticles = (articles: Shout[]) => {
|
|||
setSortedArticles((prevSortedArticles) => [...prevSortedArticles, ...articles])
|
||||
}
|
||||
|
||||
export const loadShoutsBy = async ({
|
||||
by,
|
||||
limit,
|
||||
offset = 0
|
||||
}: {
|
||||
by: ShoutsBy
|
||||
limit: number
|
||||
offset?: number
|
||||
}): Promise<{ hasMore: boolean }> => {
|
||||
const newArticles = await apiClient.loadShoutsBy({
|
||||
by,
|
||||
limit: limit + 1,
|
||||
offset
|
||||
export const loadShout = async (slug: string): Promise<void> => {
|
||||
const newArticle = await apiClient.getShout(slug)
|
||||
addArticles([newArticle])
|
||||
}
|
||||
|
||||
export const loadShouts = async (options: LoadShoutsOptions): Promise<{ hasMore: boolean }> => {
|
||||
const newArticles = await apiClient.getShouts({
|
||||
...options,
|
||||
limit: options.limit + 1
|
||||
})
|
||||
|
||||
const hasMore = newArticles.length === limit + 1
|
||||
const hasMore = newArticles.length === options.limit + 1
|
||||
|
||||
if (hasMore) {
|
||||
newArticles.splice(-1)
|
||||
|
@ -176,7 +172,6 @@ export const useArticlesStore = (initialState: InitialState = {}) => {
|
|||
return {
|
||||
articleEntities,
|
||||
sortedArticles,
|
||||
loadShoutsBy,
|
||||
articlesByAuthor,
|
||||
articlesByLayout,
|
||||
articlesByTopic,
|
||||
|
|
|
@ -38,7 +38,7 @@ const sortedAuthors = createLazyMemo(() => {
|
|||
})
|
||||
|
||||
const addAuthors = (authors: Author[]) => {
|
||||
const newAuthorEntities = authors.reduce((acc, author) => {
|
||||
const newAuthorEntities = authors.filter(Boolean).reduce((acc, author) => {
|
||||
acc[author.slug] = author
|
||||
return acc
|
||||
}, {} as Record<string, Author>)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import type { Shout, ShoutsBy } from '../../graphql/types.gen'
|
||||
import type { Shout, LoadShoutsOptions } from '../../graphql/types.gen'
|
||||
import { apiClient } from '../../utils/apiClient'
|
||||
import { useArticlesStore } from './articles'
|
||||
import { createSignal } from 'solid-js'
|
||||
|
||||
export type LayoutType = 'article' | 'audio' | 'video' | 'image' | 'literature'
|
||||
|
@ -22,27 +21,18 @@ export const resetSortedLayoutShouts = () => {
|
|||
setSortedLayoutShouts(new Map())
|
||||
}
|
||||
|
||||
export const loadLayoutShoutsBy = async ({
|
||||
by,
|
||||
limit,
|
||||
offset
|
||||
}: {
|
||||
by: ShoutsBy
|
||||
limit?: number
|
||||
offset?: number
|
||||
}): Promise<{ hasMore: boolean }> => {
|
||||
const newLayoutShouts = await apiClient.loadShoutsBy({
|
||||
by,
|
||||
limit: limit + 1,
|
||||
offset
|
||||
export const loadLayoutShoutsBy = async (options: LoadShoutsOptions): Promise<{ hasMore: boolean }> => {
|
||||
const newLayoutShouts = await apiClient.getShouts({
|
||||
...options,
|
||||
limit: options.limit + 1
|
||||
})
|
||||
|
||||
const hasMore = newLayoutShouts.length === limit + 1
|
||||
const hasMore = newLayoutShouts.length === options.limit + 1
|
||||
|
||||
if (hasMore) {
|
||||
newLayoutShouts.splice(-1)
|
||||
}
|
||||
addLayoutShouts(by.layout as LayoutType, newLayoutShouts)
|
||||
addLayoutShouts(options.filters.layout as LayoutType, newLayoutShouts)
|
||||
|
||||
return { hasMore }
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ export const loadReactionsBy = async ({
|
|||
limit?: number
|
||||
offset?: number
|
||||
}): Promise<{ hasMore: boolean }> => {
|
||||
const data = await apiClient.loadReactionsBy({ by, limit: limit + 1, offset })
|
||||
const data = await apiClient.getReactionsBy({ by, limit: limit + 1, offset })
|
||||
const hasMore = data.length === limit + 1
|
||||
if (hasMore) data.splice(-1)
|
||||
// TODO: const [data, provider] = roomConnect(articleSlug, username, "reactions")
|
||||
|
|
|
@ -53,7 +53,7 @@ const topTopics = createMemo(() => {
|
|||
})
|
||||
|
||||
const addTopics = (...args: Topic[][]) => {
|
||||
const allTopics = args.flatMap((topics) => topics || [])
|
||||
const allTopics = args.flatMap((topics) => (topics || []).filter(Boolean))
|
||||
|
||||
const newTopicEntities = allTopics.reduce((acc, topic) => {
|
||||
acc[topic.slug] = topic
|
||||
|
|
|
@ -27,6 +27,11 @@
|
|||
|
||||
.container {
|
||||
width: auto;
|
||||
|
||||
.search-input {
|
||||
display: inline-block;
|
||||
width: 100px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,7 +44,7 @@
|
|||
text-align: center;
|
||||
|
||||
.loadMoreButton {
|
||||
padding: 0.6em 5em;
|
||||
padding: 0.6em 3em;
|
||||
width: 100%;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
@import 'globals';
|
||||
@import 'bootstrap/scss/functions';
|
||||
@import 'bootstrap/scss/variables';
|
||||
@import 'bootstrap/scss/maps';
|
||||
@import 'bootstrap/scss/vendor/rfs';
|
||||
@import 'bootstrap/scss/mixins/breakpoints';
|
||||
@import 'bootstrap/scss/mixins/grid';
|
||||
|
|
|
@ -232,7 +232,6 @@ button {
|
|||
|
||||
font-weight: 400;
|
||||
height: auto;
|
||||
margin-top: 0.6rem;
|
||||
padding: 0.6rem 1.2rem 0.6rem 1rem;
|
||||
}
|
||||
|
||||
|
@ -641,7 +640,7 @@ astro-island {
|
|||
.main-content {
|
||||
flex: 1 100%;
|
||||
min-height: 300px;
|
||||
padding-top: 100px;
|
||||
padding-top: 120px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
import type { FollowingEntity, AuthResult, ShoutInput, Topic, Author } from '../graphql/types.gen'
|
||||
import type {
|
||||
FollowingEntity,
|
||||
AuthResult,
|
||||
ShoutInput,
|
||||
Topic,
|
||||
Author,
|
||||
LoadShoutsOptions
|
||||
} from '../graphql/types.gen'
|
||||
import { publicGraphQLClient } from '../graphql/publicGraphQLClient'
|
||||
import { getToken, privateGraphQLClient } from '../graphql/privateGraphQLClient'
|
||||
import topicsAll from '../graphql/query/topics-all'
|
||||
|
@ -26,8 +33,7 @@ import reactionsLoadBy from '../graphql/query/reactions-load-by'
|
|||
import { REACTIONS_AMOUNT_PER_PAGE } from '../stores/zine/reactions'
|
||||
import authorsLoadBy from '../graphql/query/authors-load-by'
|
||||
import shoutsLoadBy from '../graphql/query/articles-load-by'
|
||||
|
||||
const FEED_SIZE = 50
|
||||
import shoutLoad from '../graphql/query/articles-load'
|
||||
|
||||
type ApiErrorCode =
|
||||
| 'unknown'
|
||||
|
@ -195,6 +201,7 @@ export const apiClient = {
|
|||
},
|
||||
getAuthor: async ({ slug }: { slug: string }): Promise<Author> => {
|
||||
const response = await publicGraphQLClient.query(authorBySlug, { slug }).toPromise()
|
||||
console.error('getAuthor', response)
|
||||
return response.data.getAuthor
|
||||
},
|
||||
getTopic: async ({ slug }: { slug: string }): Promise<Topic> => {
|
||||
|
@ -230,20 +237,33 @@ export const apiClient = {
|
|||
return response.data.deleteReaction
|
||||
},
|
||||
|
||||
// LOAD BY
|
||||
|
||||
loadAuthorsBy: async ({ by, limit = 50, offset = 0 }) => {
|
||||
getAuthorsBy: async ({ by, limit = 50, offset = 0 }) => {
|
||||
const resp = await publicGraphQLClient.query(authorsLoadBy, { by, limit, offset }).toPromise()
|
||||
console.debug(resp)
|
||||
return resp.data.loadShoutsBy
|
||||
return resp.data.loadAuthorsBy
|
||||
},
|
||||
loadShoutsBy: async ({ by, limit = 50, offset = 0 }) => {
|
||||
const resp = await publicGraphQLClient.query(shoutsLoadBy, { by, limit, offset }).toPromise()
|
||||
console.debug(resp)
|
||||
return resp.data.loadShoutsBy
|
||||
getShout: async (slug: string) => {
|
||||
const resp = await publicGraphQLClient
|
||||
.query(shoutLoad, {
|
||||
slug
|
||||
})
|
||||
.toPromise()
|
||||
return resp.data.loadShout
|
||||
},
|
||||
loadReactionsBy: async ({ by, limit = REACTIONS_AMOUNT_PER_PAGE, offset = 0 }) => {
|
||||
getShouts: async (options: LoadShoutsOptions) => {
|
||||
const resp = await publicGraphQLClient
|
||||
.query(shoutsLoadBy, {
|
||||
options
|
||||
})
|
||||
.toPromise()
|
||||
// console.debug(resp)
|
||||
return resp.data.loadShouts
|
||||
},
|
||||
getReactionsBy: async ({ by, limit = REACTIONS_AMOUNT_PER_PAGE, offset = 0 }) => {
|
||||
const resp = await publicGraphQLClient.query(reactionsLoadBy, { by, limit, offset }).toPromise()
|
||||
|
||||
console.log('resactions response', resp)
|
||||
|
||||
return resp.data.loadReactionsBy
|
||||
},
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
export const isDev = import.meta.env.MODE === 'development'
|
||||
|
||||
//export const apiBaseUrl = 'https://v2.discours.io'
|
||||
// export const apiBaseUrl = 'https://testapi.discours.io'
|
||||
// export const apiBaseUrl = 'https://v2.discours.io'
|
||||
// export const apiBaseUrl = 'https://newapi.discours.io'
|
||||
// testapi.discours.io
|
||||
export const apiBaseUrl = 'http://localhost:8080'
|
||||
|
|
41
yarn.lock
41
yarn.lock
|
@ -41,9 +41,9 @@
|
|||
node-fetch "^2.6.1"
|
||||
|
||||
"@astrojs/compiler@^0.27.0 || ^0.28.0 || ^0.29.0", "@astrojs/compiler@^0.29.15", "@astrojs/compiler@^0.29.3":
|
||||
version "0.29.16"
|
||||
resolved "https://registry.yarnpkg.com/@astrojs/compiler/-/compiler-0.29.16.tgz#453d7f4da6c2d0935743f4b7075141f619ac0a05"
|
||||
integrity sha512-1CCf+dktc8IQCdmsNNSIor3rcJE5OIirFnFtQWp1VUxqCacefqRRlsl9lH7JcKKpRvz1taL43yHYJP8dxNfVww==
|
||||
version "0.29.17"
|
||||
resolved "https://registry.yarnpkg.com/@astrojs/compiler/-/compiler-0.29.17.tgz#5c65e18fd5dde9620dcc1794a858609b66408215"
|
||||
integrity sha512-6ZbRGVunUMHxROD9Cleqkrfrj/kM9o43nLVwycdxCexCB5G372evy2ZM46LhaG/k5B5yC0PByNHTaGny0ll3iQ==
|
||||
|
||||
"@astrojs/language-server@^0.28.3":
|
||||
version "0.28.3"
|
||||
|
@ -138,12 +138,13 @@
|
|||
which-pm-runs "^1.1.0"
|
||||
|
||||
"@astrojs/vercel@^2.3.3":
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@astrojs/vercel/-/vercel-2.3.3.tgz#354aebd3e504d57d9e7794a7e8c5229885d92928"
|
||||
integrity sha512-gdYf98Oii8MEfRHyX6Uwsbvx/rjimZV75qPSMbcGPFUCteshENFvtUAk5jMJvJNrDh/Cxjt4akTd4/llbvWBeQ==
|
||||
version "2.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@astrojs/vercel/-/vercel-2.3.4.tgz#79e3658786bebfa57d1c0efdec1b4db35ee3f4c4"
|
||||
integrity sha512-1mrPdlb68Y+DPtDpOfuRlene9F2t+wICXDLoG+bII9ryURxTYCReV5JQ1uwlKQ3yizQLIKIzm0rs5yp+K5FWbw==
|
||||
dependencies:
|
||||
"@astrojs/webapi" "^1.1.1"
|
||||
"@vercel/nft" "^0.22.1"
|
||||
fast-glob "^3.2.11"
|
||||
|
||||
"@astrojs/webapi@^1.1.1":
|
||||
version "1.1.1"
|
||||
|
@ -1765,9 +1766,9 @@
|
|||
integrity sha512-1eZA1/HYOhmlZ9LrrGot+LUi/ypO2NXqfB+9F1WY98dGNDMz9pG9k+X7kg2YDJTUHDGbzY7WrsBRyAE8LurE7Q==
|
||||
|
||||
"@solid-primitives/resize-observer@^2.0.5":
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@solid-primitives/resize-observer/-/resize-observer-2.0.6.tgz#2086c92d3a5f82512ecbc47fceff02eac272bd2c"
|
||||
integrity sha512-PbYmBFJBx1/WcrTZepcr6fABOrUP6CeXxehy2AKPCJInX3LKQ/elHLsM1g6KwVbvqpZ0aQ3a/3I7sRYk6BSrpw==
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@solid-primitives/resize-observer/-/resize-observer-2.0.7.tgz#0f909ed58d5fd7ec59b2fee15ddafdd28fdce4c8"
|
||||
integrity sha512-/RtCTs61ACdsCKJodNTgnKA05CI09dkg7usAb5jg14L6mzwTNWWdZbXtbYsUlD+kh1/1j+BKxp6VtkbpgJE5yQ==
|
||||
dependencies:
|
||||
"@solid-primitives/event-listener" "^2.2.4"
|
||||
"@solid-primitives/rootless" "^1.2.1"
|
||||
|
@ -2579,9 +2580,9 @@ astro-eslint-parser@^0.9.0:
|
|||
espree "^9.0.0"
|
||||
|
||||
astro@^1.6.8:
|
||||
version "1.6.8"
|
||||
resolved "https://registry.yarnpkg.com/astro/-/astro-1.6.8.tgz#46ab77d8e968088faf8bcc2e77d2856cb1fe0bdd"
|
||||
integrity sha512-+kOj8s2fguCFCim9he6fl9iugIHrmAl7BmfNXdTdC9zU30VYV162HF5eRJyMlY5hGuDn3GvAoaNSzCMnybVsFQ==
|
||||
version "1.6.9"
|
||||
resolved "https://registry.yarnpkg.com/astro/-/astro-1.6.9.tgz#08d7aed72168f8f45fc46e3ac47dd1a8ac0e2bbc"
|
||||
integrity sha512-KXFKXobe8MIYl4gduUPLcAazMz+thox6N1pOv3F3QMbJS5rMRXkWloVK/6XebBO7p3DYkOfOGB4qA9ijTc4ftA==
|
||||
dependencies:
|
||||
"@astrojs/compiler" "^0.29.15"
|
||||
"@astrojs/language-server" "^0.28.3"
|
||||
|
@ -2640,8 +2641,8 @@ astro@^1.6.8:
|
|||
typescript "*"
|
||||
unist-util-visit "^4.1.0"
|
||||
vfile "^5.3.2"
|
||||
vite "~3.2.1"
|
||||
vitefu "^0.2.0"
|
||||
vite "~3.2.4"
|
||||
vitefu "^0.2.1"
|
||||
yargs-parser "^21.0.1"
|
||||
zod "^3.17.3"
|
||||
|
||||
|
@ -2875,10 +2876,10 @@ boolean@^3.0.1:
|
|||
resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b"
|
||||
integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==
|
||||
|
||||
bootstrap@5.1.3:
|
||||
version "5.1.3"
|
||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.1.3.tgz#ba081b0c130f810fa70900acbc1c6d3c28fa8f34"
|
||||
integrity sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==
|
||||
bootstrap@5.2.2:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.2.2.tgz#834e053eed584a65e244d8aa112a6959f56e27a0"
|
||||
integrity sha512-dEtzMTV71n6Fhmbg4fYJzQsw1N29hJKO1js5ackCgIpDcGid2ETMGC6zwSYw09v05Y+oRdQ9loC54zB1La3hHQ==
|
||||
|
||||
boxen@^6.2.1:
|
||||
version "6.2.1"
|
||||
|
@ -9703,7 +9704,7 @@ vfile@^5.0.0, vfile@^5.3.2:
|
|||
unist-util-stringify-position "^3.0.0"
|
||||
vfile-message "^3.0.0"
|
||||
|
||||
vite@^3.2.4, vite@~3.2.1:
|
||||
vite@^3.2.4, vite@~3.2.4:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-3.2.4.tgz#d8c7892dd4268064e04fffbe7d866207dd24166e"
|
||||
integrity sha512-Z2X6SRAffOUYTa+sLy3NQ7nlHFU100xwanq1WDwqaiFiCe+25zdxP1TfCS5ojPV2oDDcXudHIoPnI1Z/66B7Yw==
|
||||
|
@ -9715,7 +9716,7 @@ vite@^3.2.4, vite@~3.2.1:
|
|||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
vitefu@^0.2.0:
|
||||
vitefu@^0.2.0, vitefu@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/vitefu/-/vitefu-0.2.1.tgz#9dcd78737c77b366206706dac2403a751903907d"
|
||||
integrity sha512-clkvXTAeUf+XQKm3bhWUhT4pye+3acm6YCTGaWhxxIvZZ/QjnA3JA8Zud+z/mO5y5XYvJJhevs5Sjkv/FI8nRw==
|
||||
|
|
Loading…
Reference in New Issue
Block a user