This commit is contained in:
Untone 2024-07-05 16:34:19 +03:00
parent 22f45d8bd9
commit bff5de0c8e
10 changed files with 177 additions and 103 deletions

View File

@ -1,4 +1,4 @@
import { useLocalize } from '../../../../context/localize'
import { useLocalize } from '~/context/localize'
import { Icon } from '../../../_shared/Icon'
import { Popover } from '../../../_shared/Popover'

View File

@ -1,6 +1,6 @@
import { useSearchParams } from '@solidjs/router'
import { Show } from 'solid-js'
import { useLocalize } from '../../../../context/localize'
import { useLocalize } from '~/context/localize'
import styles from './AuthModalHeader.module.scss'
type Props = {

View File

@ -1,9 +1,7 @@
import { clsx } from 'clsx'
import { Show, createEffect, createSignal, on } from 'solid-js'
import { useLocalize } from '../../../../context/localize'
import { useLocalize } from '~/context/localize'
import { Icon } from '../../../_shared/Icon'
import styles from './PasswordField.module.scss'
type Props = {

View File

@ -1,9 +1,7 @@
import { For } from 'solid-js'
import { useLocalize } from '../../../../context/localize'
import { useSession } from '../../../../context/session'
import { useLocalize } from '~/context/localize'
import { useSession } from '~/context/session'
import { Icon } from '../../../_shared/Icon'
import styles from './SocialProviders.module.scss'
export const PROVIDERS = ['facebook', 'google', 'github'] // 'vk' | 'telegram'

View File

@ -1,16 +1,16 @@
import { A, useLocation, useNavigate, useSearchParams } from '@solidjs/router'
import { clsx } from 'clsx'
import { For, Show, createEffect, createMemo, createSignal, onCleanup, onMount } from 'solid-js'
import { Icon } from '~/components/_shared/Icon'
import { Newsletter } from '~/components/_shared/Newsletter'
import { For, Show, createEffect, createSignal, onCleanup, onMount } from 'solid-js'
import { useLocalize } from '~/context/localize'
import { useSession } from '~/context/session'
import { useTopics } from '~/context/topics'
import { useUI } from '~/context/ui'
import type { Topic } from '~/graphql/schema/core.gen'
import { getRandomTopicsFromArray } from '~/utils/getRandomTopicsFromArray'
import { getDescription } from '~/utils/meta'
import type { Topic } from '../../../graphql/schema/core.gen'
import { getRandomTopicsFromArray } from '../../../utils/getRandomTopicsFromArray'
import { getDescription } from '../../../utils/meta'
import { SharePopup, getShareUrl } from '../../Article/SharePopup'
import { Icon } from '../../_shared/Icon'
import { Newsletter } from '../../_shared/Newsletter'
import { AuthModal } from '../AuthModal'
import { ConfirmModal } from '../ConfirmModal'
import { HeaderAuth } from '../HeaderAuth'
@ -18,6 +18,7 @@ import { Modal } from '../Modal'
import { SearchModal } from '../SearchModal/SearchModal'
import { Snackbar } from '../Snackbar'
import styles from './Header.module.scss'
import { Link } from './HeaderLink'
type Props = {
title?: string
@ -32,27 +33,29 @@ type HeaderSearchParams = {
source?: string
}
const handleSwitchLanguage = (value: string) => {
location.href = `${location.href}${location.href.includes('?') ? '&' : '?'}lng=${value}`
const handleSwitchLanguage = (event: { target: { value: string } }) => {
location.href = `${location.href}${location.href.includes('?') ? '&' : '?'}lng=${event.target.value}`
}
export const Header = (props: Props) => {
const { t, lang } = useLocalize()
const { modal } = useUI()
const navigate = useNavigate()
const [searchParams] = useSearchParams<HeaderSearchParams>()
const { requireAuthentication } = useSession()
const { sortedTopics } = useTopics()
const topics = createMemo<Topic[]>(() => sortedTopics())
const [searchParams, ] = useSearchParams<HeaderSearchParams>()
const { sortedTopics: topics } = useTopics()
const [randomTopics, setRandomTopics] = createSignal<Topic[]>([])
const [getIsScrollingBottom, setIsScrollingBottom] = createSignal(false)
const [getIsScrolled, setIsScrolled] = createSignal(false)
const [fixed, setFixed] = createSignal(false)
const [isSharePopupVisible, setIsSharePopupVisible] = createSignal(false)
const [isProfilePopupVisible, setIsProfilePopupVisible] = createSignal(false)
const [isTopMenuVisible, setIsTopMenuVisible] = createSignal(false)
const [isKnowledgeBaseVisible, setIsKnowledgeBaseVisible] = createSignal(false)
const [isTopicsVisible, setIsTopicsVisible] = createSignal(false)
const [isZineVisible, setIsZineVisible] = createSignal(false)
const [isFeedVisible, setIsFeedVisible] = createSignal(false)
const { session } = useSession()
const loc = useLocation()
const navigate = useNavigate()
const toggleFixed = () => setFixed(!fixed())
const tag = (topic: Topic) =>
@ -62,29 +65,24 @@ export const Header = (props: Props) => {
createEffect(() => {
if (topics()?.length) {
const rt: Topic[] = getRandomTopicsFromArray(topics())
setRandomTopics(rt)
setRandomTopics(getRandomTopicsFromArray(topics()))
}
})
createEffect(() => {
const mainContent = document.querySelector<HTMLDivElement>('.main-content')
if ((window && fixed()) || modal() !== null) {
if (fixed() || modal() !== null) {
windowScrollTop = window.scrollY
if (mainContent) {
mainContent.style.marginTop = `-${windowScrollTop}px`
}
if (mainContent) mainContent.style.marginTop = `-${windowScrollTop}px`
}
document.body.classList.toggle('fixed', fixed() || modal() !== null)
document.body.classList.toggle(styles.fixed, fixed() && !modal())
if (!(fixed() || modal())) {
if (mainContent) {
mainContent.style.marginTop = ''
}
window.scrollTo(0, windowScrollTop)
if(mainContent) mainContent.style.marginTop = ''
}
})
@ -103,19 +101,19 @@ export const Header = (props: Props) => {
})
})
const scrollToComments = (event: MouseEvent | undefined, value: boolean) => {
event?.preventDefault()
const scrollToComments = (event: MouseEvent & { currentTarget: HTMLDivElement; target: Element }, value: boolean) => {
event.preventDefault()
props.scrollToComments?.(value)
}
const handleBookmarkButtonClick = (ev: MouseEvent | undefined) => {
const handleBookmarkButtonClick = (ev: { preventDefault: () => void }) => {
requireAuthentication(() => {
// TODO: implement bookmark clicked
ev?.preventDefault()
ev.preventDefault()
}, 'bookmark')
}
const handleCreateButtonClick = (ev: MouseEvent | undefined) => {
const handleCreateButtonClick = (ev?: { preventDefault: () => void }) => {
requireAuthentication(() => {
ev?.preventDefault()
@ -123,9 +121,12 @@ export const Header = (props: Props) => {
}, 'create')
}
const toggleSubnavigation = (isShow: boolean, signal?: (v: boolean) => void) => {
const toggleSubnavigation = (isShow: boolean, signal?: (b: boolean) => void) => {
clearTimer()
setIsTopMenuVisible(false)
setIsKnowledgeBaseVisible(false)
setIsTopicsVisible(false)
setIsZineVisible(false)
setIsFeedVisible(false)
if (signal) {
signal(isShow)
@ -138,17 +139,21 @@ export const Header = (props: Props) => {
clearTimeout(timer)
}
const hideSubnavigation = (time = 500) => {
const hideSubnavigation = (_ev?: MouseEvent, time = 500) => {
timer = setTimeout(() => {
toggleSubnavigation(false)
}, time)
}
const loc = useLocation()
const handleToggleMenuByLink = (event: MouseEvent, route: string) => {
console.debug('toggling menu link', fixed(), route)
event.preventDefault()
if (loc.pathname === route) {
toggleFixed()
}
navigate(route)
}
return (
<header
class={styles.mainHeader}
@ -163,7 +168,7 @@ export const Header = (props: Props) => {
<Modal
variant={searchParams?.source ? 'narrow' : 'wide'}
name="auth"
hideClose={Boolean(searchParams?.source === 'authguard')}
hideClose={searchParams?.source === 'authguard'}
noPadding={true}
>
<AuthModal />
@ -185,7 +190,7 @@ export const Header = (props: Props) => {
</div>
</div>
<div class={clsx('col-md-5 col-xl-4 col-auto', styles.mainLogo)}>
<A href={'/'}>
<A href='/'>
<img src="/logo.svg" alt={t('Discours')} />
</A>
</div>
@ -195,34 +200,58 @@ export const Header = (props: Props) => {
</Show>
<div class={clsx(styles.mainNavigation, { [styles.fixed]: fixed() })}>
<ul class="view-switcher">
<For each={['', 'feed', 'topics', 'authors', 'guide']}>
{(route: string) => (
<li classList={{ 'view-switcher__item--selected': loc.pathname.includes(route) }}>
<A
class={clsx({ [styles.mainNavigationItemActive]: loc.pathname.includes(route) })}
href={`/${route}`}
onClick={(event) => handleToggleMenuByLink(event, `/${route}`)}
onMouseOver={() => toggleSubnavigation(true, setIsTopMenuVisible)}
onMouseOut={() => hideSubnavigation()}
>
{t(route || 'journal')}
</A>
</li>
)}
</For>
<Link
onMouseOver={() => toggleSubnavigation(true, setIsZineVisible)}
onMouseOut={hideSubnavigation}
href="/"
active={isZineVisible()}
body={t('journal')}
onClick={(event: MouseEvent) => handleToggleMenuByLink(event, '')}
/>
<Link
onMouseOver={() => toggleSubnavigation(true, setIsFeedVisible)}
onMouseOut={hideSubnavigation}
href="/feed"
active={isFeedVisible()}
body={t('feed')}
onClick={(event: MouseEvent) => handleToggleMenuByLink(event, 'feed')}
/>
<Link
onMouseOver={() => toggleSubnavigation(true, setIsTopicsVisible)}
onMouseOut={hideSubnavigation}
href="/topic"
active={isTopicsVisible()}
body={t('topics')}
onClick={(event: MouseEvent) => handleToggleMenuByLink(event, 'topic')}
/>
<Link
onMouseOver={(event?: MouseEvent) => hideSubnavigation(event, 0)}
onMouseOut={(event?: MouseEvent) => hideSubnavigation(event, 0)}
href="/author"
body={t('authors')}
onClick={(event: MouseEvent) => handleToggleMenuByLink(event, 'author')}
/>
<Link
onMouseOver={() => toggleSubnavigation(true, setIsKnowledgeBaseVisible)}
onMouseOut={() => hideSubnavigation}
href="/guide"
body={t('Knowledge base')}
active={isKnowledgeBaseVisible()}
onClick={(event: MouseEvent) => handleToggleMenuByLink(event, 'guide')}
/>
</ul>
<div class={styles.mainNavigationMobile}>
<h4>{t('Participating')}</h4>
<ul class="view-switcher">
<li>
<A href="/edit/new">{t('Create post')}</A>
<a href="/create">{t('Create post')}</a>
</li>
<li>
<A href="/connect">{t('Suggest an idea')}</A>
<a href="/connect">{t('Suggest an idea')}</a>
</li>
<li>
<A href="/support">{t('Support the project')}</A>
<a href="/about/help">{t('Support the project')}</a>
</li>
</ul>
@ -272,7 +301,7 @@ export const Header = (props: Props) => {
<h4>{t('Language')}</h4>
<select
class={styles.languageSelectorMobile}
onChange={(ev) => handleSwitchLanguage(ev.target.value)}
onChange={handleSwitchLanguage}
value={lang()}
>
<option value="ru">🇷🇺 Русский</option>
@ -280,9 +309,9 @@ export const Header = (props: Props) => {
</select>
<div class={styles.mainNavigationAdditionalLinks}>
<A href="/guide/dogma">{t('Dogma')}</A>
<A href="/guide/debate">{t('Discussion rules')}</A>
<A href="/guide/principles">{t('Our principles')}</A>
<a href="/about/dogma">{t('Dogma')}</a>
<a href="/about/discussion-rules" innerHTML={t('Discussion rules')} />
<a href="/about/principles">{t('Principles')}</a>
</div>
<p
@ -337,73 +366,74 @@ export const Header = (props: Props) => {
<div
class={clsx(styles.subnavigation, 'col')}
classList={{ hidden: !isKnowledgeBaseVisible() }}
onMouseOver={clearTimer}
onMouseOut={() => hideSubnavigation()}
onMouseOut={hideSubnavigation}
>
<ul class="nodash">
<li>
<A href="/guide/manifest">{t('Manifesto')}</A>
<a href="/about/manifest">{t('Manifesto')}</a>
</li>
<li>
<A href="/guide/dogma">{t('Dogma')}</A>
<a href="/about/dogma">{t('Dogma')}</a>
</li>
<li>
<A href="/guide/principles">{t('Community Our principles')}</A>
<a href="/about/principles">{t('Community Principles')}</a>
</li>
<li>
<A href="/guide">{t('Platform Guide')}</A>
<a href="/about/guide">{t('Platform Guide')}</a>
</li>
<li>
<A href="/support">{t('Support us')}</A>
<a href="/about/manifest#participation">{t('Support us')}</a>
</li>
<li>
<A href="/manifest#participation">{t('How to help')}</A>
<a href="/about/help">{t('How to help')}</a>
</li>
<li class={styles.rightItem}>
<A href="/connect">
<a href="/connect">
{t('Suggest an idea')}
<Icon name="arrow-right-black" class={clsx(styles.icon, styles.rightItemIcon)} />
</A>
</a>
</li>
</ul>
</div>
<div
class={clsx(styles.subnavigation, 'col')}
classList={{ hidden: !isTopMenuVisible() }}
classList={{ hidden: !isZineVisible() }}
onMouseOver={clearTimer}
onMouseOut={() => hideSubnavigation()}
onMouseOut={hideSubnavigation}
>
<ul class="nodash">
<li class="item">
<A href="/expo">{t('Art')}</A>
<a href="/expo">{t('Art')}</a>
</li>
<li class="item">
<A href="/podcasts">{t('Podcasts')}</A>
<a href="/podcasts">{t('Podcasts')}</a>
</li>
<li class="item">
<A href="/guide/projects">{t('Special Projects')}</A>
<a href="/about/projects">{t('Special Projects')}</a>
</li>
<li>
<A href="/topic/interview">#{t('Interview')}</A>
<a href="/topic/interview">#{t('Interview')}</a>
</li>
<li>
<A href="/topic/reportage">#{t('Reports')}</A>
<a href="/topic/reportage">#{t('Reports')}</a>
</li>
<li>
<A href="/topic/empiric">#{t('Experience')}</A>
<a href="/topic/empiric">#{t('Experience')}</a>
</li>
<li>
<A href="/topic/society">#{t('Society')}</A>
<a href="/topic/society">#{t('Society')}</a>
</li>
<li>
<A href="/topic/culture">#{t('Culture')}</A>
<a href="/topic/culture">#{t('Culture')}</a>
</li>
<li>
<A href="/topic/theory">#{t('Theory')}</A>
<a href="/topic/theory">#{t('Theory')}</a>
</li>
<li>
<A href="/topic/poetry">#{t('Poetry')}</A>
<a href="/topic/poetry">#{t('Poetry')}</a>
</li>
<li class={styles.rightItem}>
<A href="/topic">
@ -416,8 +446,9 @@ export const Header = (props: Props) => {
<div
class={clsx(styles.subnavigation, 'col')}
classList={{ hidden: !isTopicsVisible() }}
onMouseOver={clearTimer}
onMouseOut={() => hideSubnavigation()}
onMouseOut={hideSubnavigation}
>
<ul class="nodash">
<Show when={randomTopics().length > 0}>
@ -442,8 +473,9 @@ export const Header = (props: Props) => {
<div
class={clsx(styles.subnavigation, styles.subnavigationFeed, 'col')}
classList={{ hidden: !isFeedVisible() }}
onMouseOver={clearTimer}
onMouseOut={() => hideSubnavigation()}
onMouseOut={hideSubnavigation}
>
<ul class="nodash">
<li>

View File

@ -0,0 +1,36 @@
import { A, useLocation } from '@solidjs/router'
import { clsx } from 'clsx'
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
import styles from './Header.module.scss'
type Props = {
onMouseOver: (event?: MouseEvent, time?: number) => void
onMouseOut: (event?: MouseEvent, time?: number) => void
href?: string
body: string
active?: boolean
onClick?: (event: MouseEvent) => void
}
export const Link = (props: Props) => {
const loc = useLocation()
return (
<li
onClick={props.onClick}
classList={{ 'view-switcher__item--selected': props.href === `/${loc.pathname}` }}
>
<ConditionalWrapper
condition={props.href === `/${loc.pathname}`}
wrapper={(children) => <A href={props.href||'/'}>{children}</A>}
>
<span
class={clsx('cursorPointer linkReplacement', { [styles.mainNavigationItemActive]: props.active })}
onMouseOver={props.onMouseOver}
onMouseOut={props.onMouseOut}
>
{props.body}
</span>
</ConditionalWrapper>
</li>
)
}

View File

@ -120,7 +120,7 @@ export const AllAuthors = (props: Props) => {
<div class="row">
<div class="col-lg-20 col-xl-18">
<ul class={clsx('nodash', styles.alphabet)}>
<For each={[...alphabet()]}>
<For each={[...(alphabet()||[])]}>
{(letter, index) => (
<li>
<Show when={letter in byLetterFiltered()} fallback={letter}>
@ -140,15 +140,15 @@ export const AllAuthors = (props: Props) => {
</ul>
</div>
</div>
<For each={sortedKeys()}>
<For each={sortedKeys() || []}>
{(letter) => (
<div class={clsx(styles.group, 'group')}>
<h2 id={`letter-${alphabet().indexOf(letter)}`}>{letter}</h2>
<h2 id={`letter-${alphabet()?.indexOf(letter)||''}`}>{letter}</h2>
<div class="container">
<div class="row">
<div class="col-lg-20">
<div class="row">
<For each={byLetterFiltered()[letter]}>
<For each={byLetterFiltered()?.[letter] || []}>
{(author) => (
<div class={clsx(styles.topic, 'topic col-sm-12 col-md-8')}>
<div class="topic-title">

View File

@ -83,11 +83,11 @@ export const LocalizeProvider = (props: { children: JSX.Element }) => {
const formatTimeAgo = (date: Date) => timeAgo().format(date)
const value: LocalizeContextType = {
t: ((s: string) => {
t: ((...args) => {
try {
return i18next.t(s)
return i18next.t(...args)
} catch (_) {
return s
return args?.length > 0 ? args[0] as string : ''
}
}) as i18n['t'],
lang,

View File

@ -10,30 +10,40 @@ import ru from '~/config/locales/ru/translation.json'
TimeAgo.addLocale(enTime)
TimeAgo.addLocale(ruTime)
class AutoKeyMap extends Map<string, string> {
get(key: string): string {
return super.get(key) ?? key;
}
}
export const i18nextInit = async (lng = 'ru') => {
if (!i18next.isInitialized) {
console.debug('[i18next] initializing...')
// eslint-disable-next-line import/no-named-as-default-member
const enAutoKeyMap = new AutoKeyMap(Object.entries(en));
await i18next
.use(HttpApi)
.use(ICU)
.init({
// debug: true,
supportedLngs: ['ru', 'en'],
fallbackLng: lng,
fallbackLng: 'en',
lng,
load: 'languageOnly',
initImmediate: false,
resources: {
ru: { translation: ru },
en: { translation: en }
}
en: { translation: enAutoKeyMap }
},
interpolation: {
escapeValue: false
},
parseMissingKeyHandler: (key) => key
})
// console.debug(i18next)
} else if (i18next.language !== lng) {
await i18next.changeLanguage(lng)
}
// console.debug(`[i18next] using <${lng}>...`)
}
export { TimeAgo, i18next, type i18n }