ClientContainer -> ShowOnlyOnClient, topic and author page fixes

This commit is contained in:
Igor Lobanov 2022-11-15 17:16:31 +01:00
parent ac3b4b82d8
commit 28fa18b7c0
27 changed files with 162 additions and 368 deletions

View File

@ -33,7 +33,7 @@ export const Beside = (props: BesideProps) => {
<h4>{props.title}</h4>
<Show when={props.wrapper === 'author'}>
<a href="/user/list">
<a href="/authors">
{t('All authors')}
<Icon name="arrow-right" />
</a>

View File

@ -6,7 +6,6 @@ import { translit } from '../../utils/ru2en'
import { Icon } from '../Nav/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'
@ -113,7 +112,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>

View File

@ -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>

View File

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

View File

@ -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>

View File

@ -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 './Icon'
import { createSignal, onMount, 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>
)
}

View File

@ -4,7 +4,6 @@ import { Icon } from './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>

View File

@ -8,8 +8,6 @@ import { loadAuthor } from '../../stores/zine/authors'
import { Loading } from '../Loading'
export const AuthorPage = (props: PageProps) => {
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.authorArticles) && Boolean(props.author))
const slug = createMemo(() => {
const { page: getPage } = useRouter()
@ -22,6 +20,10 @@ export const AuthorPage = (props: PageProps) => {
return page.params.slug
})
const [isLoaded, setIsLoaded] = createSignal(
Boolean(props.authorArticles) && props?.author?.slug === slug()
)
onMount(async () => {
if (isLoaded()) {
return

View File

@ -8,8 +8,6 @@ import { loadTopic } from '../../stores/zine/topics'
import { Loading } from '../Loading'
export const TopicPage = (props: PageProps) => {
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.topicArticles) && Boolean(props.topic))
const slug = createMemo(() => {
const { page: getPage } = useRouter()
@ -22,6 +20,8 @@ export const TopicPage = (props: PageProps) => {
return page.params.slug
})
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.topicArticles) && props?.topic.slug === slug())
onMount(async () => {
if (isLoaded()) {
return

View File

@ -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>

View File

@ -67,28 +67,28 @@ export const GuidePage = () => {
<p>
Дискурс&nbsp;&mdash; независимый журнал о&nbsp;культуре, науке, искусстве и&nbsp;обществе
с&nbsp;<a href="/about/manifest">открытой редакцией</a>. У&nbsp;нас нет главного редактора,
инвестора и&nbsp;вообще никого, кто&nbsp;бы принимал единоличные решения. Вместо традиционных
иерархий Дискурс основан на&nbsp;принципах прямой демократии: в&nbsp;нашем горизонтальном
сообществе все редакционные вопросы решаются открытым голосованием авторов журнала. Вот как
это работает.
с&nbsp;
<a href="/about/manifest">открытой редакцией</a>. У&nbsp;нас нет главного редактора, инвестора
и&nbsp;вообще никого, кто&nbsp;бы принимал единоличные решения. Вместо традиционных иерархий
Дискурс основан на&nbsp;принципах прямой демократии: в&nbsp;нашем горизонтальном сообществе
все редакционные вопросы решаются открытым голосованием авторов журнала. Вот как это работает.
</p>
<h3 id="how-it-works">Как устроен сайт Дискурса</h3>
<p>Дискурс состоит из&nbsp;четырех основных разделов:</p>
<ul>
<li>
<p>
<a href="/topics">Темы</a>&nbsp;&mdash; у&nbsp;нас публикуются исследования, обзоры, эссе,
интервью, репортажи, аналитика и&nbsp;другие материалы о&nbsp;культуре, науке, искусстве
и&nbsp;обществе.
<a href="/topics">Темы</a>
&nbsp;&mdash; у&nbsp;нас публикуются исследования, обзоры, эссе, интервью, репортажи,
аналитика и&nbsp;другие материалы о&nbsp;культуре, науке, искусстве и&nbsp;обществе.
</p>
</li>
<li>
<p>
<a href="/topic/art">Искусство</a>&nbsp;&mdash; здесь, например, представлены
художественные произведения: литература, живопись, музыка, фотографии, видео. Этот раздел
помогает прозвучать новому искусству, которое создают российские художники, писатели,
режиссёры и&nbsp;музыканты.
<a href="/topic/art">Искусство</a>
&nbsp;&mdash; здесь, например, представлены художественные произведения: литература,
живопись, музыка, фотографии, видео. Этот раздел помогает прозвучать новому искусству,
которое создают российские художники, писатели, режиссёры и&nbsp;музыканты.
</p>
</li>
{/*
@ -118,14 +118,16 @@ export const GuidePage = () => {
&mdash;&nbsp;ключевым словам, которые располагаются в&nbsp;конце материалов и&nbsp;связывают
материалы по&nbsp;жанрам (например,
<a href="/topic/interview">интервью</a>, <a href="/topic/reportage">репортажи</a>,{' '}
<a href="/topic/essay">эссе</a>, <a href="/topic/likbez">ликбезы</a>), по&nbsp;тематике (
<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> и&nbsp;т.д.) или в&nbsp;серии (как &laquo;
<a href="/topic/zakony-mira">Законы мира</a>&raquo; или &laquo;
<a href="/topic/za-liniey-mannergeyma">За&nbsp;линией Маннергейма</a>&raquo;). Темы объединяют
сотни публикаций, помогают ориентироваться в&nbsp;журнале и&nbsp;следить за&nbsp;интересными
материалами.
<a href="/topic/essay">эссе</a>, <a href="/topic/likbez">ликбезы</a>
), по&nbsp;тематике (<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> и&nbsp;т.д.) или
в&nbsp;серии (как &laquo;
<a href="/topic/zakony-mira">Законы мира</a>
&raquo; или &laquo;
<a href="/topic/za-liniey-mannergeyma">За&nbsp;линией Маннергейма</a>
&raquo;). Темы объединяют сотни публикаций, помогают ориентироваться в&nbsp;журнале
и&nbsp;следить за&nbsp;интересными материалами.
</p>
<section>

View File

@ -91,10 +91,11 @@ export const ManifestPage = () => {
<p>
Редакция Дискурса открыта для всех: у&nbsp;нас нет цензуры, запретных тем
и&nbsp;идеологических рамок. Каждый может <a href="/create">прислать материал</a>{' '}
в&nbsp;журнал и&nbsp;<a href="/about/guide">присоединиться к&nbsp;редакции</a>. Предоставляя
трибуну для независимой журналистики и&nbsp;художественных проектов, мы&nbsp;помогаем людям
рассказывать свои истории так, чтобы они были услышаны. Мы&nbsp;убеждены: чем больше голосов
будет звучать на&nbsp;Дискурсе, тем громче в&nbsp;полифонии мнений будет слышна истина.
в&nbsp;журнал и&nbsp;
<a href="/about/guide">присоединиться к&nbsp;редакции</a>. Предоставляя трибуну для
независимой журналистики и&nbsp;художественных проектов, мы&nbsp;помогаем людям рассказывать
свои истории так, чтобы они были услышаны. Мы&nbsp;убеждены: чем больше голосов будет звучать
на&nbsp;Дискурсе, тем громче в&nbsp;полифонии мнений будет слышна истина.
</p>
<h2 class="h2" id="participation">

View File

@ -269,7 +269,7 @@ export const TermsOfUsePage = () => {
<a href="mailto:welcome@discours.io" target="_blank">
welcome@discours.io
</a>{' '}
или через форму <a href="/feedback-idea">&laquo;предложить идею&raquo;</a>.
или через форму <a href="/connect">&laquo;предложить идею&raquo;</a>.
</p>
</div>
</div>

View File

@ -76,11 +76,7 @@ export const ThanksPage = () => {
признательны всем, кто нас поддерживает. Ваши пожертвования&nbsp;&mdash; финансовый фундамент
журнала. Благодаря вам мы&nbsp;развиваем платформу качественной журналистики, которая помогает
самым разным авторам быть услышанными. Стать нашим меценатом и&nbsp;подписаться
на&nbsp;ежемесячную поддержку проекта можно{' '}
<a href="/about/help" target="_self">
здесь
</a>
.
на&nbsp;ежемесячную поддержку проекта можно <a href="/about/help">здесь</a>.
</p>
</div>
</div>

View File

@ -58,7 +58,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>

View File

@ -4,7 +4,7 @@ import { AuthorCard } from '../Author/Card'
import { Icon } from '../Nav/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'
@ -70,19 +70,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">

View File

@ -3,7 +3,7 @@ import type { Topic } from '../../graphql/types.gen'
import { Icon } from '../Nav/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'
@ -69,14 +69,10 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
<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>
<a href="/topics?by=shouts">{t('By shouts')}</a>
</li>
<li classList={{ selected: searchParams().by === 'authors' }}>
<a href="/topics?by=authors" onClick={handleClientRouteLinkClick}>
{t('By authors')}
</a>
<a href="/topics?by=authors">{t('By authors')}</a>
</li>
<li classList={{ selected: searchParams().by === 'title' }}>
<a

View File

@ -18,7 +18,7 @@ type AuthorProps = {
authorArticles: Shout[]
author: Author
authorSlug: string
// FIXME author topics fro server
// FIXME author topics from server
// topics: Topic[]
}

View File

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

View File

@ -76,13 +76,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>
@ -93,7 +93,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>

View File

@ -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 = () => {
<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 = () => {
</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>

View File

@ -4,7 +4,7 @@ import type { Shout } from '../../graphql/types.gen'
import { ArticleCard } from '../Feed/Card'
import { t } from '../../utils/intl'
import { useArticlesStore, loadSearchResults } from '../../stores/zine/articles'
import { handleClientRouteLinkClick, useRouter } from '../../stores/router'
import { useRouter } from '../../stores/router'
type SearchPageSearchParams = {
by: '' | 'relevance' | 'rating'
@ -51,18 +51,14 @@ export const SearchView = (props: Props) => {
selected: searchParams().by === 'relevance'
}}
>
<a href="?by=relevance" onClick={handleClientRouteLinkClick}>
{t('By relevance')}
</a>
<a href="?by=relevance">{t('By relevance')}</a>
</li>
<li
classList={{
selected: searchParams().by === 'rating'
}}
>
<a href="?by=rating" onClick={handleClientRouteLinkClick}>
{t('Top rated')}
</a>
<a href="?by=rating">{t('Top rated')}</a>
</li>
</ul>

View File

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

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

View File

@ -41,6 +41,17 @@ export type AuthorStat = {
rating?: Maybe<Scalars['Int']>
}
export type AuthorsBy = {
createdAt?: InputMaybe<Scalars['DateTime']>
days?: InputMaybe<Scalars['Int']>
lastSeen?: InputMaybe<Scalars['DateTime']>
name?: InputMaybe<Scalars['String']>
order?: InputMaybe<Scalars['String']>
slug?: InputMaybe<Scalars['String']>
stat?: InputMaybe<Scalars['String']>
topic?: InputMaybe<Scalars['String']>
}
export type Chat = {
admins?: Maybe<Array<Maybe<User>>>
createdAt: Scalars['Int']
@ -88,12 +99,6 @@ export type Collection = {
title: Scalars['String']
}
export type CollectionInput = {
desc?: InputMaybe<Scalars['String']>
pic?: InputMaybe<Scalars['String']>
title: Scalars['String']
}
export type Community = {
createdAt: Scalars['DateTime']
createdBy: User
@ -104,12 +109,6 @@ export type Community = {
slug: Scalars['String']
}
export type CommunityInput = {
desc?: InputMaybe<Scalars['String']>
pic?: InputMaybe<Scalars['String']>
title: Scalars['String']
}
export enum FollowingEntity {
Author = 'AUTHOR',
Community = 'COMMUNITY',
@ -133,25 +132,28 @@ export enum MessageStatus {
Updated = 'UPDATED'
}
export type MessagesBy = {
author?: InputMaybe<Scalars['String']>
body?: InputMaybe<Scalars['String']>
chat?: InputMaybe<Scalars['String']>
days?: InputMaybe<Scalars['Int']>
order?: InputMaybe<Scalars['String']>
stat?: InputMaybe<Scalars['String']>
}
export type Mutation = {
confirmEmail: AuthResult
createChat: Result
createCollection: Result
createCommunity: Result
createMessage: Result
createReaction: Result
createShout: Result
createTopic: Result
deleteChat: Result
deleteCollection: Result
deleteCommunity: Result
deleteMessage: Result
deleteReaction: Result
deleteShout: Result
destroyTopic: Result
enterChat: Result
follow: Result
incrementView: Result
inviteAuthor: Result
inviteChat: Result
markAsRead: Result
@ -162,9 +164,8 @@ export type Mutation = {
sendLink: Result
unfollow: Result
updateChat: Result
updateCollection: Result
updateCommunity: Result
updateMessage: Result
updateOnlineStatus: Result
updateProfile: Result
updateReaction: Result
updateShout: Result
@ -180,14 +181,6 @@ export type MutationCreateChatArgs = {
title?: InputMaybe<Scalars['String']>
}
export type MutationCreateCollectionArgs = {
collection: CollectionInput
}
export type MutationCreateCommunityArgs = {
community: CommunityInput
}
export type MutationCreateMessageArgs = {
body: Scalars['String']
chatId: Scalars['String']
@ -210,14 +203,6 @@ export type MutationDeleteChatArgs = {
chatId: Scalars['String']
}
export type MutationDeleteCollectionArgs = {
slug: Scalars['String']
}
export type MutationDeleteCommunityArgs = {
slug: Scalars['String']
}
export type MutationDeleteMessageArgs = {
chatId: Scalars['String']
id: Scalars['Int']
@ -235,19 +220,11 @@ export type MutationDestroyTopicArgs = {
slug: Scalars['String']
}
export type MutationEnterChatArgs = {
chatId: Scalars['String']
}
export type MutationFollowArgs = {
slug: Scalars['String']
what: FollowingEntity
}
export type MutationIncrementViewArgs = {
shout: Scalars['String']
}
export type MutationInviteAuthorArgs = {
author: Scalars['String']
shout: Scalars['String']
@ -293,14 +270,6 @@ export type MutationUpdateChatArgs = {
chat: ChatInput
}
export type MutationUpdateCollectionArgs = {
collection: CollectionInput
}
export type MutationUpdateCommunityArgs = {
community: CommunityInput
}
export type MutationUpdateMessageArgs = {
body: Scalars['String']
chatId: Scalars['String']
@ -348,98 +317,66 @@ export type ProfileInput = {
export type Query = {
authorsAll: Array<Maybe<Author>>
collectionsAll: Array<Maybe<Collection>>
getAuthor: User
getCollabs: Array<Maybe<Collab>>
getCommunities: Array<Maybe<Community>>
getCommunity: Community
getShoutBySlug: Shout
getTopic: Topic
getUserCollections: Array<Maybe<Collection>>
getUserRoles: Array<Maybe<Role>>
getUsersBySlugs: Array<Maybe<Author>>
isEmailUsed: Scalars['Boolean']
loadAuthorsBy: Array<Maybe<Author>>
loadChats: Result
loadMessages: Result
loadMessagesBy: Result
loadReactionsBy: Array<Maybe<Reaction>>
loadShoutsBy: Array<Maybe<Shout>>
markdownBody: Scalars['String']
reactionsByAuthor: Array<Maybe<Reaction>>
reactionsForShouts: Array<Maybe<Reaction>>
recentAll: Array<Maybe<Shout>>
recentCandidates: Array<Maybe<Shout>>
recentCommented: Array<Maybe<Shout>>
recentLayoutShouts: Array<Maybe<Shout>>
recentPublished: Array<Maybe<Shout>>
recentReacted: Array<Maybe<Shout>>
searchChats: Result
searchMessages: Result
searchQuery?: Maybe<Array<Maybe<Shout>>>
searchUsers: Result
shoutsByAuthors: Array<Maybe<Shout>>
shoutsByCollection: Array<Maybe<Shout>>
shoutsByCommunities: Array<Maybe<Shout>>
shoutsByLayout: Array<Maybe<Shout>>
shoutsByTopics: Array<Maybe<Shout>>
shoutsForFeed: Array<Maybe<Shout>>
signIn: AuthResult
signOut: AuthResult
topAuthors: Array<Maybe<Author>>
topCommented: Array<Maybe<Shout>>
topLayoutShouts: Array<Maybe<Shout>>
topMonth: Array<Maybe<Shout>>
topMonthLayoutShouts: Array<Maybe<Shout>>
topOverall: Array<Maybe<Shout>>
topPublished: Array<Maybe<Shout>>
topicsAll: Array<Maybe<Topic>>
topicsByAuthor: Array<Maybe<Topic>>
topicsByCommunity: Array<Maybe<Topic>>
topicsRandom: Array<Maybe<Topic>>
userFollowedAuthors: Array<Maybe<Author>>
userFollowedCommunities: Array<Maybe<Community>>
userFollowedTopics: Array<Maybe<Topic>>
userFollowers: Array<Maybe<Author>>
userReactedShouts: Array<Maybe<Shout>>
}
export type QueryGetAuthorArgs = {
slug: Scalars['String']
}
export type QueryGetCommunityArgs = {
slug?: InputMaybe<Scalars['String']>
}
export type QueryGetShoutBySlugArgs = {
slug: Scalars['String']
}
export type QueryGetTopicArgs = {
slug: Scalars['String']
}
export type QueryGetUserCollectionsArgs = {
author: Scalars['String']
}
export type QueryGetUserRolesArgs = {
slug: Scalars['String']
}
export type QueryGetUsersBySlugsArgs = {
slugs: Array<InputMaybe<Scalars['String']>>
}
export type QueryIsEmailUsedArgs = {
email: Scalars['String']
}
export type QueryLoadAuthorsByArgs = {
amount?: InputMaybe<Scalars['Int']>
by?: InputMaybe<AuthorsBy>
offset?: InputMaybe<Scalars['Int']>
}
export type QueryLoadChatsArgs = {
amount?: InputMaybe<Scalars['Int']>
offset?: InputMaybe<Scalars['Int']>
}
export type QueryLoadMessagesArgs = {
export type QueryLoadMessagesByArgs = {
amount?: InputMaybe<Scalars['Int']>
chatId: Scalars['String']
by: MessagesBy
offset?: InputMaybe<Scalars['Int']>
}
export type QueryLoadReactionsByArgs = {
amount?: InputMaybe<Scalars['Int']>
by: ReactionBy
limit?: InputMaybe<Scalars['Int']>
}
export type QueryLoadShoutsByArgs = {
amount?: InputMaybe<Scalars['Int']>
by?: InputMaybe<ShoutsBy>
offset?: InputMaybe<Scalars['Int']>
}
@ -447,106 +384,10 @@ export type QueryMarkdownBodyArgs = {
body: Scalars['String']
}
export type QueryReactionsByAuthorArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
slug: Scalars['String']
}
export type QueryReactionsForShoutsArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
shouts: Array<InputMaybe<Scalars['String']>>
}
export type QueryRecentAllArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryRecentCandidatesArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryRecentCommentedArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryRecentLayoutShoutsArgs = {
amount?: InputMaybe<Scalars['Int']>
layout: Scalars['String']
offset?: InputMaybe<Scalars['Int']>
}
export type QueryRecentPublishedArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryRecentReactedArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QuerySearchChatsArgs = {
amount?: InputMaybe<Scalars['Int']>
offset?: InputMaybe<Scalars['Int']>
q: Scalars['String']
}
export type QuerySearchMessagesArgs = {
amount?: InputMaybe<Scalars['Int']>
offset?: InputMaybe<Scalars['Int']>
q: Scalars['String']
}
export type QuerySearchQueryArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
q?: InputMaybe<Scalars['String']>
}
export type QuerySearchUsersArgs = {
amount?: InputMaybe<Scalars['Int']>
offset?: InputMaybe<Scalars['Int']>
q: Scalars['String']
}
export type QueryShoutsByAuthorsArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
slugs: Array<InputMaybe<Scalars['String']>>
}
export type QueryShoutsByCollectionArgs = {
collection: Scalars['String']
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryShoutsByCommunitiesArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
slugs: Array<InputMaybe<Scalars['String']>>
}
export type QueryShoutsByLayoutArgs = {
amount: Scalars['Int']
layout?: InputMaybe<Scalars['String']>
offset: Scalars['Int']
}
export type QueryShoutsByTopicsArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
slugs: Array<InputMaybe<Scalars['String']>>
}
export type QueryShoutsForFeedArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
query: Scalars['String']
}
export type QuerySignInArgs = {
@ -555,44 +396,6 @@ export type QuerySignInArgs = {
password?: InputMaybe<Scalars['String']>
}
export type QueryTopAuthorsArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryTopCommentedArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryTopLayoutShoutsArgs = {
amount?: InputMaybe<Scalars['Int']>
layout: Scalars['String']
offset?: InputMaybe<Scalars['Int']>
}
export type QueryTopMonthArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryTopMonthLayoutShoutsArgs = {
amount?: InputMaybe<Scalars['Int']>
layout: Scalars['String']
offset?: InputMaybe<Scalars['Int']>
}
export type QueryTopOverallArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryTopPublishedArgs = {
daysago: Scalars['Int']
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryTopicsByAuthorArgs = {
author: Scalars['String']
}
@ -609,10 +412,6 @@ export type QueryUserFollowedAuthorsArgs = {
slug: Scalars['String']
}
export type QueryUserFollowedCommunitiesArgs = {
slug: Scalars['String']
}
export type QueryUserFollowedTopicsArgs = {
slug: Scalars['String']
}
@ -621,10 +420,6 @@ export type QueryUserFollowersArgs = {
slug: Scalars['String']
}
export type QueryUserReactedShoutsArgs = {
slug: Scalars['String']
}
export type Rating = {
rater: Scalars['String']
value: Scalars['Int']
@ -647,6 +442,16 @@ export type Reaction = {
updatedAt?: Maybe<Scalars['DateTime']>
}
export type ReactionBy = {
author?: InputMaybe<Scalars['String']>
body?: InputMaybe<Scalars['String']>
days?: InputMaybe<Scalars['Int']>
order?: InputMaybe<Scalars['String']>
shout?: InputMaybe<Scalars['String']>
stat?: InputMaybe<Scalars['String']>
topic?: InputMaybe<Scalars['String']>
}
export type ReactionInput = {
body?: InputMaybe<Scalars['String']>
kind: Scalars['Int']
@ -757,6 +562,19 @@ export type ShoutInput = {
visibleForUsers?: InputMaybe<Array<InputMaybe<Scalars['String']>>>
}
export type ShoutsBy = {
author?: 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']>
topic?: InputMaybe<Scalars['String']>
visibility?: InputMaybe<Scalars['String']>
}
export type Stat = {
commented?: Maybe<Scalars['Int']>
ranking?: Maybe<Scalars['Int']>

View File

@ -59,7 +59,7 @@ const routerStore = createRouter<Routes>(
export const router = routerStore
export const handleClientRouteLinkClick = (event) => {
const handleClientRouteLinkClick = (event) => {
const link = event.target.closest('a')
if (
link &&
@ -92,6 +92,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) {

View File

@ -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