Merge pull request #58 from Discours/client-routing-minor-fixes

ClientContainer -> ShowOnlyOnClient, topic and author page fixes
This commit is contained in:
Tony 2022-11-17 09:59:34 +03:00 committed by GitHub
commit ee06ab54d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 107 additions and 11096 deletions

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -6,7 +6,6 @@ import { translit } from '../../utils/ru2en'
import { Icon } from '../_shared/Icon' import { Icon } from '../_shared/Icon'
import styles from './Card.module.scss' import styles from './Card.module.scss'
import { locale } from '../../stores/ui' import { locale } from '../../stores/ui'
import { handleClientRouteLinkClick } from '../../stores/router'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import CardTopic from './CardTopic' import CardTopic from './CardTopic'
@ -115,7 +114,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
</Show> </Show>
<div class={styles.shoutCardTitlesContainer}> <div class={styles.shoutCardTitlesContainer}>
<a href={`/${slug || ''}`} onClick={handleClientRouteLinkClick}> <a href={`/${slug || ''}`}>
<div class={styles.shoutCardTitle}> <div class={styles.shoutCardTitle}>
<span class={styles.shoutCardLinkContainer}>{title}</span> <span class={styles.shoutCardLinkContainer}>{title}</span>
</div> </div>

View File

@ -3,7 +3,7 @@ import { clsx } from 'clsx'
import { t } from '../../../utils/intl' import { t } from '../../../utils/intl'
import { hideModal } from '../../../stores/ui' import { hideModal } from '../../../stores/ui'
import { createMemo, createSignal, onMount, Show } from 'solid-js' 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 type { ConfirmEmailSearchParams } from './types'
import { ApiError } from '../../../utils/apiClient' import { ApiError } from '../../../utils/apiClient'
import { useSession } from '../../../context/session' import { useSession } from '../../../context/session'
@ -48,7 +48,7 @@ export const EmailConfirm = () => {
<Show when={isTokenExpired()}> <Show when={isTokenExpired()}>
<div class={styles.title}>Ссылка больше не действительна</div> <div class={styles.title}>Ссылка больше не действительна</div>
<div class={styles.text}> <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*/} {/*TODO: temp solution, should be send link again, but we don't have email here*/}
Вход Вход
</a> </a>
@ -57,7 +57,7 @@ export const EmailConfirm = () => {
<Show when={isTokenInvalid()}> <Show when={isTokenInvalid()}>
<div class={styles.title}>Неправильная ссылка</div> <div class={styles.title}>Неправильная ссылка</div>
<div class={styles.text}> <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*/} {/*TODO: temp solution, should be send link again, but we don't have email here*/}
Вход Вход
</a> </a>

View File

@ -2,7 +2,7 @@ import { Dynamic } from 'solid-js/web'
import { Component, createEffect, createMemo } from 'solid-js' import { Component, createEffect, createMemo } from 'solid-js'
import { t } from '../../../utils/intl' import { t } from '../../../utils/intl'
import { hideModal } from '../../../stores/ui' import { hideModal } from '../../../stores/ui'
import { handleClientRouteLinkClick, useRouter } from '../../../stores/router' import { useRouter } from '../../../stores/router'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import styles from './AuthModal.module.scss' import styles from './AuthModal.module.scss'
import { LoginForm } from './LoginForm' import { LoginForm } from './LoginForm'
@ -57,9 +57,8 @@ export const AuthModal = () => {
{t('By signing up you agree with our')}{' '} {t('By signing up you agree with our')}{' '}
<a <a
href="/about/terms-of-use" href="/about/terms-of-use"
onClick={(event) => { onClick={() => {
hideModal() hideModal()
handleClientRouteLinkClick(event)
}} }}
> >
{t('terms of use')} {t('terms of use')}

View File

@ -4,7 +4,7 @@ import { Modal } from './Modal'
import { AuthModal } from './AuthModal' import { AuthModal } from './AuthModal'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { useModalStore } from '../../stores/ui' 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 styles from './Header.module.scss'
import { getPagePath } from '@nanostores/router' import { getPagePath } from '@nanostores/router'
import { clsx } from 'clsx' import { clsx } from 'clsx'
@ -91,7 +91,7 @@ export const Header = (props: Props) => {
<div class={clsx(styles.mainHeaderInner, 'wide-container')}> <div class={clsx(styles.mainHeaderInner, 'wide-container')}>
<nav class={clsx(styles.headerInner, 'row')} classList={{ fixed: fixed() }}> <nav class={clsx(styles.headerInner, 'row')} classList={{ fixed: fixed() }}>
<div class={clsx(styles.mainLogo, 'col-auto')}> <div class={clsx(styles.mainLogo, 'col-auto')}>
<a href={getPagePath(router, 'home')} onClick={handleClientRouteLinkClick}> <a href={getPagePath(router, 'home')}>
<img src="/logo.svg" alt={t('Discours')} /> <img src="/logo.svg" alt={t('Discours')} />
</a> </a>
</div> </div>
@ -107,9 +107,7 @@ export const Header = (props: Props) => {
<For each={resources}> <For each={resources}>
{(r) => ( {(r) => (
<li classList={{ [styles.selected]: r.route === page().route }}> <li classList={{ [styles.selected]: r.route === page().route }}>
<a href={getPagePath(router, r.route, null)} onClick={handleClientRouteLinkClick}> <a href={getPagePath(router, r.route, null)}>{r.name}</a>
{r.name}
</a>
</li> </li>
)} )}
</For> </For>

View File

@ -1,6 +1,6 @@
import styles from './Header.module.scss' import styles from './Header.module.scss'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { handleClientRouteLinkClick, useRouter } from '../../stores/router' import { useRouter } from '../../stores/router'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { Icon } from '../_shared/Icon' import { Icon } from '../_shared/Icon'
import { createSignal, Show } from 'solid-js' import { createSignal, Show } from 'solid-js'
@ -9,7 +9,7 @@ import { ProfilePopup } from './ProfilePopup'
import Userpic from '../Author/Userpic' import Userpic from '../Author/Userpic'
import type { Author } from '../../graphql/types.gen' import type { Author } from '../../graphql/types.gen'
import { showModal, useWarningsStore } from '../../stores/ui' import { showModal, useWarningsStore } from '../../stores/ui'
import { ClientContainer } from '../_shared/ClientContainer' import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
import { useSession } from '../../context/session' import { useSession } from '../../context/session'
type HeaderAuthProps = { type HeaderAuthProps = {
@ -37,12 +37,12 @@ export const HeaderAuth = (props: HeaderAuthProps) => {
} }
return ( return (
<ClientContainer> <ShowOnlyOnClient>
<Show when={!session.loading}> <Show when={!session.loading}>
<div class={styles.usernav}> <div class={styles.usernav}>
<div class={clsx(styles.userControl, styles.userControl, 'col')}> <div class={clsx(styles.userControl, styles.userControl, 'col')}>
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}> <div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
<a href="/create" onClick={handleClientRouteLinkClick}> <a href="/create">
<span class={styles.textLabel}>{t('Create post')}</span> <span class={styles.textLabel}>{t('Create post')}</span>
<Icon name="pencil" class={styles.icon} /> <Icon name="pencil" class={styles.icon} />
</a> </a>
@ -68,7 +68,7 @@ export const HeaderAuth = (props: HeaderAuthProps) => {
when={isAuthenticated()} when={isAuthenticated()}
fallback={ fallback={
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose, 'loginbtn')}> <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> <span class={styles.textLabel}>{t('Enter')}</span>
<Icon name="user-anonymous" class={styles.icon} /> <Icon name="user-anonymous" class={styles.icon} />
</a> </a>
@ -102,6 +102,6 @@ export const HeaderAuth = (props: HeaderAuthProps) => {
</div> </div>
</div> </div>
</Show> </Show>
</ClientContainer> </ShowOnlyOnClient>
) )
} }

View File

@ -4,7 +4,6 @@ import { Icon } from '../_shared/Icon'
import './Topics.scss' import './Topics.scss'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { locale } from '../../stores/ui' import { locale } from '../../stores/ui'
import { handleClientRouteLinkClick } from '../../stores/router'
export const NavTopics = (props: { topics: Topic[] }) => { export const NavTopics = (props: { topics: Topic[] }) => {
const tag = (topic: Topic) => const tag = (topic: Topic) =>
@ -18,7 +17,7 @@ export const NavTopics = (props: { topics: Topic[] }) => {
<For each={props.topics}> <For each={props.topics}>
{(topic) => ( {(topic) => (
<li class="item"> <li class="item">
<a href={`/topic/${topic.slug}`} onClick={handleClientRouteLinkClick}> <a href={`/topic/${topic.slug}`}>
<span>#{tag(topic)}</span> <span>#{tag(topic)}</span>
</a> </a>
</li> </li>

View File

@ -40,10 +40,7 @@ export const DogmaPage = () => {
<li> <li>
<b>Всегда исправляем ошибки, если мы их допустили.</b> <b>Всегда исправляем ошибки, если мы их допустили.</b>
Никто не безгрешен, иногда и мы ошибаемся. Заметили ошибку - отправьте{' '} Никто не безгрешен, иногда и мы ошибаемся. Заметили ошибку - отправьте{' '}
<a href="/about/guide#editing" target="_self"> <a href="/about/guide#editing">ремарку</a> автору или напишите нам на{' '}
ремарку
</a>{' '}
автору или напишите нам на{' '}
<a href="mailto:welcome@discours.io" target="_blank"> <a href="mailto:welcome@discours.io" target="_blank">
welcome@discours.io welcome@discours.io
</a> </a>

View File

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

View File

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

View File

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

View File

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

View File

@ -60,7 +60,7 @@ export const TopicCard = (props: TopicProps) => {
</Show> </Show>
<Show when={props.topic.pic}> <Show when={props.topic.pic}>
<div class={styles.topicAvatar}> <div class={styles.topicAvatar}>
<a href={props.topic.slug}> <a href={`/topic/${props.topic.slug}`}>
<img src={props.topic.pic} alt={props.topic.title} /> <img src={props.topic.pic} alt={props.topic.title} />
</a> </a>
</div> </div>

View File

@ -4,7 +4,7 @@ import { AuthorCard } from '../Author/Card'
import { Icon } from '../_shared/Icon' import { Icon } from '../_shared/Icon'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { useAuthorsStore, setAuthorsSort } from '../../stores/zine/authors' 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 styles from '../../styles/AllTopics.module.scss'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { useSession } from '../../context/session' import { useSession } from '../../context/session'
@ -70,19 +70,13 @@ export const AllAuthorsView = (props: Props) => {
<ul class={clsx(styles.viewSwitcher, 'view-switcher')}> <ul class={clsx(styles.viewSwitcher, 'view-switcher')}>
<li classList={{ selected: searchParams().by === 'shouts' }}> <li classList={{ selected: searchParams().by === 'shouts' }}>
<a href="/authors?by=shouts" onClick={handleClientRouteLinkClick}> <a href="/authors?by=shouts">{t('By shouts')}</a>
{t('By shouts')}
</a>
</li> </li>
<li classList={{ selected: searchParams().by === 'rating' }}> <li classList={{ selected: searchParams().by === 'rating' }}>
<a href="/authors?by=rating" onClick={handleClientRouteLinkClick}> <a href="/authors?by=rating">{t('By rating')}</a>
{t('By rating')}
</a>
</li> </li>
<li classList={{ selected: !searchParams().by || searchParams().by === 'name' }}> <li classList={{ selected: !searchParams().by || searchParams().by === 'name' }}>
<a href="/authors" onClick={handleClientRouteLinkClick}> <a href="/authors">{t('By alphabet')}</a>
{t('By alphabet')}
</a>
</li> </li>
<li class="view-switcher__search"> <li class="view-switcher__search">
<a href="/authors/search"> <a href="/authors/search">

View File

@ -3,7 +3,7 @@ import type { Topic } from '../../graphql/types.gen'
import { Icon } from '../_shared/Icon' import { Icon } from '../_shared/Icon'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { setTopicsSort, useTopicsStore } from '../../stores/zine/topics' import { setTopicsSort, useTopicsStore } from '../../stores/zine/topics'
import { handleClientRouteLinkClick, useRouter } from '../../stores/router' import { useRouter } from '../../stores/router'
import { TopicCard } from '../Topic/Card' import { TopicCard } from '../Topic/Card'
import styles from '../../styles/AllTopics.module.scss' import styles from '../../styles/AllTopics.module.scss'
import { clsx } from 'clsx' import { clsx } from 'clsx'
@ -69,14 +69,10 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
<ul class={clsx(styles.viewSwitcher, 'view-switcher')}> <ul class={clsx(styles.viewSwitcher, 'view-switcher')}>
<li classList={{ selected: searchParams().by === 'shouts' || !searchParams().by }}> <li classList={{ selected: searchParams().by === 'shouts' || !searchParams().by }}>
<a href="/topics?by=shouts" onClick={handleClientRouteLinkClick}> <a href="/topics?by=shouts">{t('By shouts')}</a>
{t('By shouts')}
</a>
</li> </li>
<li classList={{ selected: searchParams().by === 'authors' }}> <li classList={{ selected: searchParams().by === 'authors' }}>
<a href="/topics?by=authors" onClick={handleClientRouteLinkClick}> <a href="/topics?by=authors">{t('By authors')}</a>
{t('By authors')}
</a>
</li> </li>
<li classList={{ selected: searchParams().by === 'title' }}> <li classList={{ selected: searchParams().by === 'title' }}>
<a <a

View File

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

View File

@ -1,11 +1,11 @@
import { Editor } from '../EditorNew/Editor' import { Editor } from '../EditorNew/Editor'
import { ClientContainer } from '../_shared/ClientContainer' import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
export const CreateView = () => { export const CreateView = () => {
return ( return (
<ClientContainer> <ShowOnlyOnClient>
<Editor /> <Editor />
</ClientContainer> </ShowOnlyOnClient>
) )
} }

View File

@ -84,13 +84,13 @@ export const FeedView = () => {
</li> </li>
</Show> </Show>
<li> <li>
<a href="?by=views">{t('Most read')}</a> <a href="/feed/?by=views">{t('Most read')}</a>
</li> </li>
<li> <li>
<a href="?by=rating">{t('Top rated')}</a> <a href="/feed/?by=rating">{t('Top rated')}</a>
</li> </li>
<li> <li>
<a href="?by=comments">{t('Most commented')}</a> <a href="/feed/?by=comments">{t('Most commented')}</a>
</li> </li>
</ul> </ul>
@ -101,7 +101,7 @@ export const FeedView = () => {
<div class={stylesBeside.besideColumnTitle}> <div class={stylesBeside.besideColumnTitle}>
<h4>{t('Popular authors')}</h4> <h4>{t('Popular authors')}</h4>
<a href="/user/list"> <a href="/authors">
{t('All authors')} {t('All authors')}
<Icon name="arrow-right" /> <Icon name="arrow-right" />
</a> </a>

View File

@ -1,6 +1,5 @@
import '../../styles/FeedSettings.scss' import '../../styles/FeedSettings.scss'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { handleClientRouteLinkClick } from '../../stores/router'
// type FeedSettingsSearchParams = { // type FeedSettingsSearchParams = {
// by: '' | 'topics' | 'authors' | 'reacted' // by: '' | 'topics' | 'authors' | 'reacted'
@ -13,9 +12,7 @@ export const FeedSettingsView = (_props) => {
<ul class="view-switcher"> <ul class="view-switcher">
<li class="selected"> <li class="selected">
<a href="?by=topics" onClick={handleClientRouteLinkClick}> <a href="?by=topics">{t('topics')}</a>
{t('topics')}
</a>
</li> </li>
{/*<li> {/*<li>
<a href="?by=collections" onClick={() => setBy('collections')}> <a href="?by=collections" onClick={() => setBy('collections')}>
@ -23,14 +20,10 @@ export const FeedSettingsView = (_props) => {
</a> </a>
</li>*/} </li>*/}
<li> <li>
<a href="?by=authors" onClick={handleClientRouteLinkClick}> <a href="?by=authors">{t('authors')}</a>
{t('authors')}
</a>
</li> </li>
<li> <li>
<a href="?by=reacted" onClick={handleClientRouteLinkClick}> <a href="?by=reacted">{t('reactions')}</a>
{t('reactions')}
</a>
</li> </li>
</ul> </ul>

View File

@ -4,7 +4,7 @@ import type { Shout } from '../../graphql/types.gen'
import { ArticleCard } from '../Feed/Card' import { ArticleCard } from '../Feed/Card'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { useArticlesStore, loadShoutsBy } from '../../stores/zine/articles' import { useArticlesStore, loadShoutsBy } from '../../stores/zine/articles'
import { handleClientRouteLinkClick, useRouter } from '../../stores/router' import { useRouter } from '../../stores/router'
type SearchPageSearchParams = { type SearchPageSearchParams = {
by: '' | 'relevance' | 'rating' by: '' | 'relevance' | 'rating'
@ -19,7 +19,7 @@ export const SearchView = (props: Props) => {
const { sortedArticles } = useArticlesStore({ shouts: props.results }) const { sortedArticles } = useArticlesStore({ shouts: props.results })
const [getQuery, setQuery] = createSignal(props.query) const [getQuery, setQuery] = createSignal(props.query)
const { searchParams } = useRouter<SearchPageSearchParams>() const { searchParams, handleClientRouteLinkClick } = useRouter<SearchPageSearchParams>()
const handleQueryChange = (ev) => { const handleQueryChange = (ev) => {
setQuery(ev.target.value) setQuery(ev.target.value)
@ -51,18 +51,14 @@ export const SearchView = (props: Props) => {
selected: searchParams().by === 'relevance' selected: searchParams().by === 'relevance'
}} }}
> >
<a href="?by=relevance" onClick={handleClientRouteLinkClick}> <a href="?by=relevance">{t('By relevance')}</a>
{t('By relevance')}
</a>
</li> </li>
<li <li
classList={{ classList={{
selected: searchParams().by === 'rating' selected: searchParams().by === 'rating'
}} }}
> >
<a href="?by=rating" onClick={handleClientRouteLinkClick}> <a href="?by=rating">{t('Top rated')}</a>
{t('Top rated')}
</a>
</li> </li>
</ul> </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

@ -63,7 +63,7 @@ const routerStore = createRouter<Routes>(
export const router = routerStore export const router = routerStore
export const handleClientRouteLinkClick = (event) => { const handleClientRouteLinkClick = (event) => {
const link = event.target.closest('a') const link = event.target.closest('a')
if ( if (
link && link &&
@ -96,6 +96,10 @@ export const initRouter = (pathname: string, search: string) => {
routerStore.open(pathname) routerStore.open(pathname)
const params = Object.fromEntries(new URLSearchParams(search)) const params = Object.fromEntries(new URLSearchParams(search))
searchParamsStore.open(params) searchParamsStore.open(params)
if (!isServer) {
document.addEventListener('click', handleClientRouteLinkClick)
}
} }
if (!isServer) { if (!isServer) {
@ -125,6 +129,7 @@ export const useRouter = <TSearchParams extends Record<string, string> = Record<
return { return {
page, page,
searchParams, searchParams,
changeSearchParam changeSearchParam,
handleClientRouteLinkClick
} }
} }

View File

@ -53,7 +53,7 @@ const topTopics = createMemo(() => {
}) })
const addTopics = (...args: Topic[][]) => { const addTopics = (...args: Topic[][]) => {
const allTopics = args.flatMap((topics) => topics || []) const allTopics = args.flatMap((topics) => (topics || []).filter(Boolean))
const newTopicEntities = allTopics.reduce((acc, topic) => { const newTopicEntities = allTopics.reduce((acc, topic) => {
acc[topic.slug] = topic acc[topic.slug] = topic

View File

@ -41,9 +41,9 @@
node-fetch "^2.6.1" 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": "@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" version "0.29.17"
resolved "https://registry.yarnpkg.com/@astrojs/compiler/-/compiler-0.29.16.tgz#453d7f4da6c2d0935743f4b7075141f619ac0a05" resolved "https://registry.yarnpkg.com/@astrojs/compiler/-/compiler-0.29.17.tgz#5c65e18fd5dde9620dcc1794a858609b66408215"
integrity sha512-1CCf+dktc8IQCdmsNNSIor3rcJE5OIirFnFtQWp1VUxqCacefqRRlsl9lH7JcKKpRvz1taL43yHYJP8dxNfVww== integrity sha512-6ZbRGVunUMHxROD9Cleqkrfrj/kM9o43nLVwycdxCexCB5G372evy2ZM46LhaG/k5B5yC0PByNHTaGny0ll3iQ==
"@astrojs/language-server@^0.28.3": "@astrojs/language-server@^0.28.3":
version "0.28.3" version "0.28.3"
@ -138,12 +138,13 @@
which-pm-runs "^1.1.0" which-pm-runs "^1.1.0"
"@astrojs/vercel@^2.3.3": "@astrojs/vercel@^2.3.3":
version "2.3.3" version "2.3.4"
resolved "https://registry.yarnpkg.com/@astrojs/vercel/-/vercel-2.3.3.tgz#354aebd3e504d57d9e7794a7e8c5229885d92928" resolved "https://registry.yarnpkg.com/@astrojs/vercel/-/vercel-2.3.4.tgz#79e3658786bebfa57d1c0efdec1b4db35ee3f4c4"
integrity sha512-gdYf98Oii8MEfRHyX6Uwsbvx/rjimZV75qPSMbcGPFUCteshENFvtUAk5jMJvJNrDh/Cxjt4akTd4/llbvWBeQ== integrity sha512-1mrPdlb68Y+DPtDpOfuRlene9F2t+wICXDLoG+bII9ryURxTYCReV5JQ1uwlKQ3yizQLIKIzm0rs5yp+K5FWbw==
dependencies: dependencies:
"@astrojs/webapi" "^1.1.1" "@astrojs/webapi" "^1.1.1"
"@vercel/nft" "^0.22.1" "@vercel/nft" "^0.22.1"
fast-glob "^3.2.11"
"@astrojs/webapi@^1.1.1": "@astrojs/webapi@^1.1.1":
version "1.1.1" version "1.1.1"
@ -1765,9 +1766,9 @@
integrity sha512-1eZA1/HYOhmlZ9LrrGot+LUi/ypO2NXqfB+9F1WY98dGNDMz9pG9k+X7kg2YDJTUHDGbzY7WrsBRyAE8LurE7Q== integrity sha512-1eZA1/HYOhmlZ9LrrGot+LUi/ypO2NXqfB+9F1WY98dGNDMz9pG9k+X7kg2YDJTUHDGbzY7WrsBRyAE8LurE7Q==
"@solid-primitives/resize-observer@^2.0.5": "@solid-primitives/resize-observer@^2.0.5":
version "2.0.6" version "2.0.7"
resolved "https://registry.yarnpkg.com/@solid-primitives/resize-observer/-/resize-observer-2.0.6.tgz#2086c92d3a5f82512ecbc47fceff02eac272bd2c" resolved "https://registry.yarnpkg.com/@solid-primitives/resize-observer/-/resize-observer-2.0.7.tgz#0f909ed58d5fd7ec59b2fee15ddafdd28fdce4c8"
integrity sha512-PbYmBFJBx1/WcrTZepcr6fABOrUP6CeXxehy2AKPCJInX3LKQ/elHLsM1g6KwVbvqpZ0aQ3a/3I7sRYk6BSrpw== integrity sha512-/RtCTs61ACdsCKJodNTgnKA05CI09dkg7usAb5jg14L6mzwTNWWdZbXtbYsUlD+kh1/1j+BKxp6VtkbpgJE5yQ==
dependencies: dependencies:
"@solid-primitives/event-listener" "^2.2.4" "@solid-primitives/event-listener" "^2.2.4"
"@solid-primitives/rootless" "^1.2.1" "@solid-primitives/rootless" "^1.2.1"
@ -2579,9 +2580,9 @@ astro-eslint-parser@^0.9.0:
espree "^9.0.0" espree "^9.0.0"
astro@^1.6.8: astro@^1.6.8:
version "1.6.8" version "1.6.9"
resolved "https://registry.yarnpkg.com/astro/-/astro-1.6.8.tgz#46ab77d8e968088faf8bcc2e77d2856cb1fe0bdd" resolved "https://registry.yarnpkg.com/astro/-/astro-1.6.9.tgz#08d7aed72168f8f45fc46e3ac47dd1a8ac0e2bbc"
integrity sha512-+kOj8s2fguCFCim9he6fl9iugIHrmAl7BmfNXdTdC9zU30VYV162HF5eRJyMlY5hGuDn3GvAoaNSzCMnybVsFQ== integrity sha512-KXFKXobe8MIYl4gduUPLcAazMz+thox6N1pOv3F3QMbJS5rMRXkWloVK/6XebBO7p3DYkOfOGB4qA9ijTc4ftA==
dependencies: dependencies:
"@astrojs/compiler" "^0.29.15" "@astrojs/compiler" "^0.29.15"
"@astrojs/language-server" "^0.28.3" "@astrojs/language-server" "^0.28.3"
@ -2640,8 +2641,8 @@ astro@^1.6.8:
typescript "*" typescript "*"
unist-util-visit "^4.1.0" unist-util-visit "^4.1.0"
vfile "^5.3.2" vfile "^5.3.2"
vite "~3.2.1" vite "~3.2.4"
vitefu "^0.2.0" vitefu "^0.2.1"
yargs-parser "^21.0.1" yargs-parser "^21.0.1"
zod "^3.17.3" zod "^3.17.3"
@ -9703,7 +9704,7 @@ vfile@^5.0.0, vfile@^5.3.2:
unist-util-stringify-position "^3.0.0" unist-util-stringify-position "^3.0.0"
vfile-message "^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" version "3.2.4"
resolved "https://registry.yarnpkg.com/vite/-/vite-3.2.4.tgz#d8c7892dd4268064e04fffbe7d866207dd24166e" resolved "https://registry.yarnpkg.com/vite/-/vite-3.2.4.tgz#d8c7892dd4268064e04fffbe7d866207dd24166e"
integrity sha512-Z2X6SRAffOUYTa+sLy3NQ7nlHFU100xwanq1WDwqaiFiCe+25zdxP1TfCS5ojPV2oDDcXudHIoPnI1Z/66B7Yw== integrity sha512-Z2X6SRAffOUYTa+sLy3NQ7nlHFU100xwanq1WDwqaiFiCe+25zdxP1TfCS5ojPV2oDDcXudHIoPnI1Z/66B7Yw==
@ -9715,7 +9716,7 @@ vite@^3.2.4, vite@~3.2.1:
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"
vitefu@^0.2.0: vitefu@^0.2.0, vitefu@^0.2.1:
version "0.2.1" version "0.2.1"
resolved "https://registry.yarnpkg.com/vitefu/-/vitefu-0.2.1.tgz#9dcd78737c77b366206706dac2403a751903907d" resolved "https://registry.yarnpkg.com/vitefu/-/vitefu-0.2.1.tgz#9dcd78737c77b366206706dac2403a751903907d"
integrity sha512-clkvXTAeUf+XQKm3bhWUhT4pye+3acm6YCTGaWhxxIvZZ/QjnA3JA8Zud+z/mO5y5XYvJJhevs5Sjkv/FI8nRw== integrity sha512-clkvXTAeUf+XQKm3bhWUhT4pye+3acm6YCTGaWhxxIvZZ/QjnA3JA8Zud+z/mO5y5XYvJJhevs5Sjkv/FI8nRw==