webapp/src/components/Nav/Header/Header.tsx

461 lines
16 KiB
TypeScript
Raw Normal View History

import { For, Show, createSignal, createEffect, onMount, onCleanup } from 'solid-js'
import { getPagePath, redirectPage } from '@nanostores/router'
2022-09-28 21:08:14 +00:00
import { clsx } from 'clsx'
2023-08-26 09:41:21 +00:00
import { Modal } from '../Modal'
import { AuthModal } from '../AuthModal'
import { HeaderAuth } from '../HeaderAuth'
import { ConfirmModal } from '../ConfirmModal'
import { getShareUrl, SharePopup } from '../../Article/SharePopup'
import { Snackbar } from '../Snackbar'
import { Icon } from '../../_shared/Icon'
import type { Topic } from '../../../graphql/types.gen'
2023-08-26 09:41:21 +00:00
import { useModalStore } from '../../../stores/ui'
import { router, useRouter } from '../../../stores/router'
2023-08-26 09:41:21 +00:00
import { getDescription } from '../../../utils/meta'
2023-08-26 09:41:21 +00:00
import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session'
2022-09-09 11:53:35 +00:00
import styles from './Header.module.scss'
import { apiClient } from '../../../utils/apiClient'
import { RANDOM_TOPICS_COUNT } from '../../Views/Home'
2022-09-22 09:37:49 +00:00
type Props = {
title?: string
2023-04-17 10:31:20 +00:00
slug?: string
2022-09-23 21:29:32 +00:00
isHeaderFixed?: boolean
articleBody?: string
cover?: string
2023-04-17 10:31:20 +00:00
scrollToComments?: (value: boolean) => void
2022-09-22 09:37:49 +00:00
}
type HeaderSearchParams = {
source?: string
}
2022-09-22 09:37:49 +00:00
export const Header = (props: Props) => {
const { t, lang } = useLocalize()
2023-02-17 09:21:02 +00:00
const { modal } = useModalStore()
2023-08-13 12:27:30 +00:00
const {
actions: { requireAuthentication }
} = useSession()
const { page, searchParams } = useRouter<HeaderSearchParams>()
const [randomTopics, setRandomTopics] = createSignal([])
2022-09-15 18:18:07 +00:00
const [getIsScrollingBottom, setIsScrollingBottom] = createSignal(false)
2022-09-15 19:41:05 +00:00
const [getIsScrolled, setIsScrolled] = createSignal(false)
2022-09-09 11:53:35 +00:00
const [fixed, setFixed] = createSignal(false)
2022-10-25 15:36:32 +00:00
const [isSharePopupVisible, setIsSharePopupVisible] = createSignal(false)
const [isProfilePopupVisible, setIsProfilePopupVisible] = createSignal(false)
2023-08-17 20:28:24 +00:00
const [isKnowledgeBaseVisible, setIsKnowledgeBaseVisible] = createSignal(false)
const [isTopicsVisible, setIsTopicsVisible] = createSignal(false)
const [isZineVisible, setIsZineVisible] = createSignal(false)
const [isFeedVisible, setIsFeedVisible] = createSignal(false)
2022-10-25 15:36:32 +00:00
const toggleFixed = () => setFixed((oldFixed) => !oldFixed)
const tag = (topic: Topic) =>
/[ЁА-яё]/.test(topic.title || '') && lang() !== 'ru' ? topic.slug : topic.title
let windowScrollTop = 0
2022-09-09 11:53:35 +00:00
createEffect(() => {
2022-11-04 13:58:42 +00:00
const mainContent = document.querySelector('.main-content') as HTMLDivElement
if (fixed() || modal() !== null) {
windowScrollTop = window.scrollY
mainContent.style.marginTop = `-${windowScrollTop}px`
}
2022-10-25 16:25:42 +00:00
document.body.classList.toggle('fixed', fixed() || modal() !== null)
2022-10-25 21:17:35 +00:00
document.body.classList.toggle(styles.fixed, fixed() && !modal())
if (!fixed() && !modal()) {
mainContent.style.marginTop = ''
window.scrollTo(0, windowScrollTop)
}
2022-10-25 15:36:32 +00:00
})
2022-09-09 11:53:35 +00:00
2022-09-14 21:07:47 +00:00
onMount(() => {
2022-09-15 18:18:07 +00:00
let scrollTop = window.scrollY
2022-09-14 21:07:47 +00:00
2022-09-15 18:18:07 +00:00
const handleScroll = () => {
setIsScrollingBottom(window.scrollY > scrollTop)
2022-09-15 19:41:05 +00:00
setIsScrolled(window.scrollY > 0)
2022-09-15 18:18:07 +00:00
scrollTop = window.scrollY
}
2022-09-14 21:07:47 +00:00
2022-09-15 18:18:07 +00:00
window.addEventListener('scroll', handleScroll, { passive: true })
onCleanup(() => {
window.removeEventListener('scroll', handleScroll)
})
})
2022-09-14 21:07:47 +00:00
2023-04-20 14:01:15 +00:00
const scrollToComments = (event, value) => {
event.preventDefault()
2023-04-17 10:31:20 +00:00
props.scrollToComments(value)
}
2023-08-13 12:27:30 +00:00
const handleBookmarkButtonClick = (ev) => {
requireAuthentication(() => {
// TODO: implement bookmark clicked
ev.preventDefault()
}, 'bookmark')
}
const handleCreateButtonClick = (ev) => {
requireAuthentication(() => {
ev.preventDefault()
redirectPage(router, 'create')
}, 'create')
}
2023-08-29 06:33:30 +00:00
const toggleSubnavigation = (isShow, signal?) => {
2023-08-28 22:15:31 +00:00
clearTimer()
2023-08-17 20:28:24 +00:00
setIsKnowledgeBaseVisible(false)
setIsTopicsVisible(false)
setIsZineVisible(false)
setIsFeedVisible(false)
2023-08-28 22:15:31 +00:00
if (signal) {
signal(isShow)
}
}
2023-08-29 06:33:30 +00:00
let timer
2023-08-28 22:15:31 +00:00
const clearTimer = () => {
clearTimeout(timer)
}
2023-09-04 20:45:02 +00:00
const hideSubnavigation = (ev, time = 500) => {
2023-08-28 22:15:31 +00:00
timer = setTimeout(() => {
toggleSubnavigation(false)
2023-09-04 20:45:02 +00:00
}, time)
2023-08-17 20:28:24 +00:00
}
onMount(async () => {
const topics = await apiClient.getRandomTopics({ amount: RANDOM_TOPICS_COUNT })
setRandomTopics(topics)
})
2022-09-09 11:53:35 +00:00
return (
2022-09-15 18:18:07 +00:00
<header
2022-09-28 21:08:14 +00:00
class={styles.mainHeader}
2022-09-15 18:18:07 +00:00
classList={{
2022-09-28 21:08:14 +00:00
[styles.headerFixed]: props.isHeaderFixed,
2022-09-29 20:10:00 +00:00
[styles.headerScrolledTop]: !getIsScrollingBottom() && getIsScrolled(),
[styles.headerScrolledBottom]:
(getIsScrollingBottom() && getIsScrolled() && !isProfilePopupVisible()) || isSharePopupVisible(),
2022-10-04 10:48:11 +00:00
[styles.headerWithTitle]: Boolean(props.title)
2022-09-15 18:18:07 +00:00
}}
>
<Modal variant={searchParams().source ? 'narrow' : 'wide'} name="auth" noPadding={true}>
2022-09-09 11:53:35 +00:00
<AuthModal />
</Modal>
2022-10-17 20:53:04 +00:00
<Modal variant="narrow" name="confirm">
<ConfirmModal />
</Modal>
2022-10-17 20:53:04 +00:00
<div class={clsx(styles.mainHeaderInner, 'wide-container')}>
<nav class={clsx('row', styles.headerInner, { ['fixed']: fixed() })}>
2023-06-28 20:18:43 +00:00
<div class={clsx('col-md-5 col-xl-4 col-auto', styles.mainLogo)}>
<a href={getPagePath(router, 'home')}>
2022-09-09 11:53:35 +00:00
<img src="/logo.svg" alt={t('Discours')} />
</a>
</div>
2023-09-04 20:45:02 +00:00
<div class={clsx('col col-md-13 col-lg-12 offset-xl-1', styles.mainNavigationWrapper)}>
2022-10-03 21:44:21 +00:00
<Show when={props.title}>
<div class={styles.articleHeader}>{props.title}</div>
</Show>
2023-05-22 22:01:04 +00:00
<ul class={clsx('view-switcher', styles.mainNavigation)} classList={{ fixed: fixed() }}>
<li classList={{ 'view-switcher__item--selected': page().route === 'home' }}>
2023-08-17 20:28:24 +00:00
<a
2023-09-04 20:45:02 +00:00
classList={{ [styles.mainNavigationItemActive]: isZineVisible() }}
2023-08-28 22:15:31 +00:00
onMouseOver={() => toggleSubnavigation(true, setIsZineVisible)}
onMouseOut={hideSubnavigation}
2023-08-17 20:28:24 +00:00
href={getPagePath(router, 'home')}
>
{t('zine')}
</a>
</li>
<li classList={{ 'view-switcher__item--selected': page().route.startsWith('feed') }}>
2023-08-17 20:28:24 +00:00
<a
2023-09-04 20:45:02 +00:00
classList={{ [styles.mainNavigationItemActive]: isFeedVisible() }}
2023-08-28 22:15:31 +00:00
onMouseOver={() => toggleSubnavigation(true, setIsFeedVisible)}
onMouseOut={hideSubnavigation}
2023-08-17 20:28:24 +00:00
href={getPagePath(router, 'feed')}
>
{t('feed')}
</a>
</li>
<li classList={{ 'view-switcher__item--selected': page().route === 'topics' }}>
2023-08-17 20:28:24 +00:00
<a
2023-09-04 20:45:02 +00:00
classList={{ [styles.mainNavigationItemActive]: isTopicsVisible() }}
2023-08-28 22:15:31 +00:00
onMouseOver={() => toggleSubnavigation(true, setIsTopicsVisible)}
onMouseOut={hideSubnavigation}
2023-08-17 20:28:24 +00:00
href={getPagePath(router, 'topics')}
>
{t('topics')}
</a>
</li>
<li classList={{ 'view-switcher__item--selected': page().route === 'authors' }}>
2023-09-04 20:45:02 +00:00
<a
onMouseOver={(ev) => hideSubnavigation(ev, 0)}
onMouseOut={(ev) => hideSubnavigation(ev, 0)}
href={getPagePath(router, 'authors')}
>
{t('community')}
</a>
2023-08-17 20:28:24 +00:00
</li>
<li>
2023-08-28 22:15:31 +00:00
<a
2023-09-04 20:45:02 +00:00
classList={{ [styles.mainNavigationItemActive]: isKnowledgeBaseVisible() }}
2023-08-28 22:15:31 +00:00
onMouseOver={() => toggleSubnavigation(true, setIsKnowledgeBaseVisible)}
onMouseOut={hideSubnavigation}
>
2023-08-17 20:28:24 +00:00
{t('Knowledge base')}
</a>
</li>
2022-09-15 19:41:05 +00:00
</ul>
</div>
<HeaderAuth setIsProfilePopupVisible={setIsProfilePopupVisible} />
<Show when={props.title}>
2023-05-24 21:51:47 +00:00
<div class={clsx(styles.articleControls, 'col-auto')}>
<SharePopup
title={props.title}
imageUrl={props.cover}
2023-01-31 13:58:28 +00:00
shareUrl={getShareUrl()}
description={getDescription(props.articleBody)}
onVisibilityChange={(isVisible) => {
setIsSharePopupVisible(isVisible)
}}
containerCssClass={styles.control}
2023-07-09 18:34:59 +00:00
trigger={
<>
<Icon name="share-outline" class={styles.icon} />
<Icon name="share-outline-hover" class={clsx(styles.icon, styles.iconHover)} />
</>
}
/>
2023-04-20 14:01:15 +00:00
<div onClick={(event) => scrollToComments(event, true)} class={styles.control}>
2023-07-09 18:34:59 +00:00
<Icon name="comment" class={styles.icon} />
<Icon name="comment-hover" class={clsx(styles.icon, styles.iconHover)} />
2023-04-17 10:31:20 +00:00
</div>
2023-08-13 12:27:30 +00:00
<a href="#" class={styles.control} onClick={handleCreateButtonClick}>
2022-12-04 15:10:27 +00:00
<Icon name="pencil-outline" class={styles.icon} />
2023-07-09 18:34:59 +00:00
<Icon name="pencil-outline-hover" class={clsx(styles.icon, styles.iconHover)} />
2022-12-04 15:10:27 +00:00
</a>
2023-08-13 12:27:30 +00:00
<a href="#" class={styles.control} onClick={handleBookmarkButtonClick}>
2022-12-04 15:10:27 +00:00
<Icon name="bookmark" class={styles.icon} />
2023-07-09 18:34:59 +00:00
<Icon name="bookmark-hover" class={clsx(styles.icon, styles.iconHover)} />
</a>
2022-09-09 11:53:35 +00:00
</div>
</Show>
2023-03-10 17:42:48 +00:00
<div class={clsx(styles.burgerContainer, 'col-auto')}>
2022-09-29 10:08:22 +00:00
<div class={styles.burger} classList={{ fixed: fixed() }} onClick={toggleFixed}>
2022-09-09 11:53:35 +00:00
<div />
</div>
</div>
2023-08-17 20:28:24 +00:00
2023-08-28 22:15:31 +00:00
<div
class={clsx(styles.subnavigation, 'col')}
classList={{ hidden: !isKnowledgeBaseVisible() }}
onMouseOver={clearTimer}
onMouseOut={hideSubnavigation}
>
2023-08-17 20:28:24 +00:00
<ul class="nodash">
<li>
<a href="/about/manifest">Манифест</a>
</li>
<li>
<a href="/about/dogma">Догма</a>
</li>
<li>
<a href="/about/principles">Принципы сообщества</a>
</li>
<li>
<a href="/about/guide">Гид по дискурсу</a>
</li>
<li>
2023-09-06 20:45:12 +00:00
<a href="/about/manifest#participation">Как поддержать?</a>
2023-08-17 20:28:24 +00:00
</li>
<li>
<a href="/about/help">Как помочь?</a>
</li>
<li class={styles.rightItem}>
<a href="/connect">
{t('Suggest an idea')}
<Icon name="arrow-right-black" class={clsx(styles.icon, styles.rightItemIcon)} />
</a>
</li>
</ul>
</div>
2023-08-28 22:15:31 +00:00
<div
class={clsx(styles.subnavigation, 'col')}
classList={{ hidden: !isZineVisible() }}
onMouseOver={clearTimer}
onMouseOut={hideSubnavigation}
>
2023-08-17 20:28:24 +00:00
<ul class="nodash">
<li>
<a href="/topic/interview">#Интервью</a>
2023-08-17 20:28:24 +00:00
</li>
<li>
<a href="/topic/reportage">#Репортажи</a>
2023-08-17 20:28:24 +00:00
</li>
<li>
<a href="/topic/empiric">#Личный опыт</a>
2023-08-17 20:28:24 +00:00
</li>
<li>
<a href="/topic/society">#Общество</a>
2023-08-17 20:28:24 +00:00
</li>
<li>
<a href="/topic/culture">#Культура</a>
2023-08-17 20:28:24 +00:00
</li>
<li>
<a href="/topic/theory">#Теории</a>
2023-08-17 20:28:24 +00:00
</li>
<li>
<a href="/topic/poetry">#Поэзия</a>
2023-08-17 20:28:24 +00:00
</li>
<li class={styles.rightItem}>
<a href="/topics">
{t('All topics')}
<Icon name="arrow-right-black" class={clsx(styles.icon, styles.rightItemIcon)} />
</a>
</li>
</ul>
</div>
2023-08-28 22:15:31 +00:00
<div
class={clsx(styles.subnavigation, 'col')}
classList={{ hidden: !isTopicsVisible() }}
onMouseOver={clearTimer}
onMouseOut={hideSubnavigation}
>
2023-08-17 20:28:24 +00:00
<ul class="nodash">
<Show when={randomTopics().length > 0}>
<For each={randomTopics()}>
{(topic) => (
<li class="item">
<a href={`/topic/${topic.slug}`}>
<span>#{tag(topic)}</span>
</a>
</li>
)}
</For>
<li class={styles.rightItem}>
<a href="/topics">
{t('All topics')}
<Icon name="arrow-right-black" class={clsx(styles.icon, styles.rightItemIcon)} />
</a>
</li>
</Show>
2023-08-17 20:28:24 +00:00
</ul>
</div>
<div
class={clsx(styles.subnavigation, styles.subnavigationFeed, 'col')}
classList={{ hidden: !isFeedVisible() }}
2023-08-28 22:15:31 +00:00
onMouseOver={clearTimer}
onMouseOut={hideSubnavigation}
2023-08-17 20:28:24 +00:00
>
<ul class="nodash">
<li>
<a
href={getPagePath(router, 'feed')}
class={clsx({
[styles.selected]: page().route === 'feed'
})}
>
<span class={styles.subnavigationItemName}>
<Icon name="feed-all" class={styles.icon} />
{t('general feed')}
</span>
</a>
</li>
<li>
<a
href={getPagePath(router, 'feedMy')}
class={clsx({
[styles.selected]: page().route === 'feedMy'
})}
>
<span class={styles.subnavigationItemName}>
<Icon name="feed-my" class={styles.icon} />
{t('My feed')}
</span>
</a>
</li>
<li>
<a
href={getPagePath(router, 'feedCollaborations')}
class={clsx({
[styles.selected]: page().route === 'feedCollaborations'
})}
>
<span class={styles.subnavigationItemName}>
<Icon name="feed-collaborate" class={styles.icon} />
{t('Accomplices')}
</span>
</a>
</li>
<li>
<a
href={getPagePath(router, 'feedDiscussions')}
class={clsx({
[styles.selected]: page().route === 'feedDiscussions'
})}
>
<span class={styles.subnavigationItemName}>
<Icon name="feed-discussion" class={styles.icon} />
{t('Discussions')}
</span>
</a>
</li>
<li>
<a
href={getPagePath(router, 'feedBookmarks')}
class={clsx({
[styles.selected]: page().route === 'feedBookmarks'
})}
>
<span class={styles.subnavigationItemName}>
<Icon name="bookmark" class={styles.icon} />
{t('Bookmarks')}
</span>
</a>
</li>
<li>
<a
href={getPagePath(router, 'feedNotifications')}
class={clsx({
[styles.selected]: page().route === 'feedNotifications'
})}
>
<span class={styles.subnavigationItemName}>
<Icon name="feed-notifications" class={styles.icon} />
{t('Notifications')}
</span>
</a>
</li>
</ul>
</div>
2022-09-09 11:53:35 +00:00
</nav>
2023-08-17 20:28:24 +00:00
2023-02-09 22:39:52 +00:00
<Snackbar />
2022-09-09 11:53:35 +00:00
</div>
</header>
)
}