home-fix+topics-wip

This commit is contained in:
Untone 2024-06-28 18:05:45 +03:00
parent a616f97fe4
commit 4e631f5f91
6 changed files with 43 additions and 41 deletions

View File

@ -155,11 +155,13 @@ export const Header = (props: Props) => {
const loc = useLocation() const loc = useLocation()
const handleToggleMenuByLink = (event: MouseEvent, route: string) => { const handleToggleMenuByLink = (event: MouseEvent, route: string) => {
event.preventDefault() event.preventDefault()
console.debug(loc.pathname, route) // console.debug('[Header] toggle menu link from', loc.pathname)
// console.debug('to', route)
if (!fixed()) return if (!fixed()) return
if (loc.pathname.startsWith(route) || loc.pathname.startsWith(`/${route}`)) { if (loc.pathname.startsWith(route) || loc.pathname.startsWith(`/${route}`)) {
toggleFixed() toggleFixed()
} }
navigate(route)
} }
return ( return (
<header <header
@ -213,7 +215,7 @@ export const Header = (props: Props) => {
routeName="home" routeName="home"
active={isZineVisible()} active={isZineVisible()}
body={t('journal')} body={t('journal')}
onClick={(event) => handleToggleMenuByLink(event, 'home')} onClick={(event) => handleToggleMenuByLink(event, '/')}
/> />
<Link <Link
onMouseOver={() => toggleSubnavigation(true, setIsFeedVisible)} onMouseOver={() => toggleSubnavigation(true, setIsFeedVisible)}
@ -221,7 +223,7 @@ export const Header = (props: Props) => {
routeName="feed" routeName="feed"
active={isFeedVisible()} active={isFeedVisible()}
body={t('feed')} body={t('feed')}
onClick={(event) => handleToggleMenuByLink(event, 'feed')} onClick={(event) => handleToggleMenuByLink(event, '/feed')}
/> />
<Link <Link
onMouseOver={() => toggleSubnavigation(true, setIsTopicsVisible)} onMouseOver={() => toggleSubnavigation(true, setIsTopicsVisible)}
@ -229,14 +231,14 @@ export const Header = (props: Props) => {
routeName="topics" routeName="topics"
active={isTopicsVisible()} active={isTopicsVisible()}
body={t('topics')} body={t('topics')}
onClick={(event) => handleToggleMenuByLink(event, 'topics')} onClick={(event) => handleToggleMenuByLink(event, '/topics')}
/> />
<Link <Link
onMouseOver={() => hideSubnavigation(0)} onMouseOver={() => hideSubnavigation(0)}
onMouseOut={() => hideSubnavigation(0)} onMouseOut={() => hideSubnavigation(0)}
routeName="authors" routeName="authors"
body={t('authors')} body={t('authors')}
onClick={(event) => handleToggleMenuByLink(event, 'authors')} onClick={(event) => handleToggleMenuByLink(event, '/authors')}
/> />
<Link <Link
onMouseOver={() => toggleSubnavigation(true, setIsKnowledgeBaseVisible)} onMouseOver={() => toggleSubnavigation(true, setIsKnowledgeBaseVisible)}
@ -244,7 +246,7 @@ export const Header = (props: Props) => {
routeName="guide" routeName="guide"
body={t('Knowledge base')} body={t('Knowledge base')}
active={isKnowledgeBaseVisible()} active={isKnowledgeBaseVisible()}
onClick={(event) => handleToggleMenuByLink(event, 'guide')} onClick={(event) => handleToggleMenuByLink(event, '/guide')}
/> />
</ul> </ul>

View File

@ -1,8 +1,9 @@
import { clsx } from 'clsx'
import { For, Show, createMemo, createSignal } from 'solid-js'
import { Meta } from '@solidjs/meta' import { Meta } from '@solidjs/meta'
import { useSearchParams } from '@solidjs/router' import { useSearchParams } from '@solidjs/router'
import { clsx } from 'clsx'
import { For, Show, createEffect, createMemo, createSignal, on, onMount } from 'solid-js'
import { useTopics } from '~/context/topics'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
import type { Topic } from '../../../graphql/schema/core.gen' import type { Topic } from '../../../graphql/schema/core.gen'
import { capitalize } from '../../../utils/capitalize' import { capitalize } from '../../../utils/capitalize'
@ -12,14 +13,13 @@ import { scrollHandler } from '../../../utils/scroll'
import { TopicBadge } from '../../Topic/TopicBadge' import { TopicBadge } from '../../Topic/TopicBadge'
import { Loading } from '../../_shared/Loading' import { Loading } from '../../_shared/Loading'
import { SearchField } from '../../_shared/SearchField' import { SearchField } from '../../_shared/SearchField'
import styles from './AllTopics.module.scss' import styles from './AllTopics.module.scss'
type Props = { type Props = {
topics: Topic[] topics: Topic[]
} }
export const TOPICS_PER_PAGE = 20 export const TOPICS_PER_PAGE = 50
export const ABC = { export const ABC = {
ru: 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ#', ru: 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ#',
en: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ#' en: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ#'
@ -28,12 +28,14 @@ export const ABC = {
export const AllTopics = (props: Props) => { export const AllTopics = (props: Props) => {
const { t, lang } = useLocalize() const { t, lang } = useLocalize()
const alphabet = createMemo(() => ABC[lang()]) const alphabet = createMemo(() => ABC[lang()])
const [searchParams] = useSearchParams<{ by?: string }>() const { setTopicsSort, sortedTopics } = useTopics()
const sortedTopics = createMemo(() => props.topics) const topics = createMemo(() => sortedTopics() || props.topics)
const [searchParams, ] = useSearchParams<{ by?: string }>()
createEffect(on(() => searchParams?.by || 'shouts', setTopicsSort, {defer: true}))
onMount(() => setTopicsSort('shouts'))
// sorted derivative // sorted derivative
const byLetter = createMemo<{ [letter: string]: Topic[] }>(() => { const byLetter = createMemo<{ [letter: string]: Topic[] }>(() => {
return sortedTopics().reduce( return topics().reduce(
(acc, topic) => { (acc, topic) => {
let letter = lang() === 'en' ? topic.slug[0].toUpperCase() : (topic?.title?.[0] || '').toUpperCase() let letter = lang() === 'en' ? topic.slug[0].toUpperCase() : (topic?.title?.[0] || '').toUpperCase()
if (/[^ËА-яё]/.test(letter) && lang() === 'ru') letter = '#' if (/[^ËА-яё]/.test(letter) && lang() === 'ru') letter = '#'
@ -63,9 +65,8 @@ export const AllTopics = (props: Props) => {
// filter // filter
const [searchQuery, setSearchQuery] = createSignal('') const [searchQuery, setSearchQuery] = createSignal('')
const filteredResults = createMemo(() => { const [filteredResults, setFilteredResults] = createSignal<Topic[]>([])
return dummyFilter(sortedTopics(), searchQuery(), lang()) createEffect(() => setFilteredResults((_prev: Topic[]) => dummyFilter(topics(), searchQuery(), lang()) as Topic[]))
})
// subcomponent // subcomponent
const AllTopicsHead = () => ( const AllTopicsHead = () => (
@ -113,7 +114,7 @@ export const AllTopics = (props: Props) => {
<Meta name="twitter:card" content="summary_large_image" /> <Meta name="twitter:card" content="summary_large_image" />
<Meta name="twitter:title" content={ogTitle} /> <Meta name="twitter:title" content={ogTitle} />
<Meta name="twitter:description" content={description} /> <Meta name="twitter:description" content={description} />
<Show when={Boolean(props.topics)} fallback={<Loading />}> <Show when={Boolean(filteredResults())} fallback={<Loading />}>
<div class="row"> <div class="row">
<div class="col-md-19 offset-md-5"> <div class="col-md-19 offset-md-5">
<AllTopicsHead /> <AllTopicsHead />

View File

@ -281,9 +281,9 @@ export const SessionProvider = (props: {
const updateProfile = async (params: UpdateProfileInput) => { const updateProfile = async (params: UpdateProfileInput) => {
const resp = await authenticate(authorizer().updateProfile, params as UpdateProfileInput) const resp = await authenticate(authorizer().updateProfile, params as UpdateProfileInput)
console.debug('[context.session] updateProfile:', resp) console.debug('[context.session] updateProfile response:', resp)
if (resp?.data) { if (resp?.data) {
console.debug(resp.data) // console.debug('[context.session] response data ', resp.data)
// FIXME: renew updated profile // FIXME: renew updated profile
return true return true
} }
@ -292,12 +292,12 @@ export const SessionProvider = (props: {
const signOut = async () => { const signOut = async () => {
const authResult: ApiResponse<GenericResponse> = await authorizer().logout() const authResult: ApiResponse<GenericResponse> = await authorizer().logout()
console.debug(authResult) // console.debug('[context.session] sign out', authResult)
if (authResult) { if (authResult) {
setSession({} as AuthToken) setSession({} as AuthToken)
setIsSessionLoaded(true) setIsSessionLoaded(true)
showSnackbar({ body: t("You've successfully logged out") }) showSnackbar({ body: t("You've successfully logged out") })
console.debug(session()) // console.debug(session())
return true return true
} }
return false return false

View File

@ -106,13 +106,15 @@ const saveTopicsToIndexedDB = async (db: IDBDatabase, topics: Topic[]) => {
await tx.done await tx.done
} }
} }
export type TopicSort = 'shouts' | 'followers' | 'authors' | 'title' | ''
export const TopicsProvider = (props: { children: JSX.Element }) => { export const TopicsProvider = (props: { children: JSX.Element }) => {
const [topicEntities, setTopicEntities] = createSignal<{ [topicSlug: string]: Topic }>({}) const [topicEntities, setTopicEntities] = createSignal<{ [topicSlug: string]: Topic }>({})
const [sortAllBy, setSortAllBy] = createSignal<'shouts' | 'followers' | 'authors' | 'title'>('shouts') const [sortedTopics, setSortedTopics] = createSignal<Topic[]>([])
const [sortAllBy, setSortAllBy] = createSignal<TopicSort>('')
const sortedTopics = createLazyMemo<Topic[]>(() => { createEffect(() => {
const topics = Object.values(topicEntities()) const topics = Object.values(topicEntities())
console.debug('[context.topics] effect trig', topics)
switch (sortAllBy()) { switch (sortAllBy()) {
case 'followers': { case 'followers': {
topics.sort(byTopicStatDesc('followers')) topics.sort(byTopicStatDesc('followers'))
@ -134,8 +136,7 @@ export const TopicsProvider = (props: { children: JSX.Element }) => {
topics.sort(byTopicStatDesc('shouts')) topics.sort(byTopicStatDesc('shouts'))
} }
} }
setSortedTopics(topics as Topic[])
return topics
}) })
const topTopics = createMemo(() => { const topTopics = createMemo(() => {
@ -188,7 +189,7 @@ export const TopicsProvider = (props: { children: JSX.Element }) => {
const isCacheValid = now - timestamp < CACHE_LIFETIME const isCacheValid = now - timestamp < CACHE_LIFETIME
const topics = isCacheValid ? req : await loadAllTopics() const topics = isCacheValid ? req : await loadAllTopics()
console.info(`[context.topics] got ${(topics as Topic[]).length || 0} topics`) console.info(`[context.topics] got ${(topics as Topic[]).length || 0} topics from idb`)
addTopics(topics as Topic[]) addTopics(topics as Topic[])
setRandomTopic(getRandomTopicsFromArray(topics || [], 1).pop()) setRandomTopic(getRandomTopicsFromArray(topics || [], 1).pop())
} }

View File

@ -33,9 +33,9 @@ export const ArticlePage = (props: RouteSectionProps<{ article: Shout }>) => {
console.info('[routes.slug] mounted, connecting ga...') console.info('[routes.slug] mounted, connecting ga...')
await loadGAScript(gaIdentity) await loadGAScript(gaIdentity)
initGA(gaIdentity) initGA(gaIdentity)
console.debug('Google Analytics connected successfully') console.debug('[routes.slug] Google Analytics connected successfully')
} catch (error) { } catch (error) {
console.warn('Failed to connect Google Analytics:', error) console.warn('[routes.slug] Failed to connect Google Analytics:', error)
} }
} }
}) })

View File

@ -1,8 +1,8 @@
import { type RouteDefinition, type RouteSectionProps, createAsync, useSearchParams } from '@solidjs/router' import { type RouteDefinition, type RouteSectionProps, createAsync } from '@solidjs/router'
import { Suspense, createEffect, createSignal, on } from 'solid-js' import { Suspense, createEffect} from 'solid-js'
import { Topic, TopicStat } from '~/graphql/schema/core.gen' import { useTopics } from '~/context/topics'
import { Topic } from '~/graphql/schema/core.gen'
import { loadTopics } from '~/lib/api' import { loadTopics } from '~/lib/api'
import { byTopicStatDesc } from '~/utils/sortby'
import { AllTopics } from '../components/Views/AllTopics' import { AllTopics } from '../components/Views/AllTopics'
import { Loading } from '../components/_shared/Loading' import { Loading } from '../components/_shared/Loading'
import { PageLayout } from '../components/_shared/PageLayout' import { PageLayout } from '../components/_shared/PageLayout'
@ -16,18 +16,16 @@ const fetchData = async () => {
export const route = { load: loadTopics } satisfies RouteDefinition export const route = { load: loadTopics } satisfies RouteDefinition
export default function HomePage(props: RouteSectionProps<{ topics: Topic[] }>) { export default function AllTopicsPage(props: RouteSectionProps<{ topics: Topic[] }>) {
const { t } = useLocalize() const { t } = useLocalize()
const topics = createAsync<Topic[]>(async () => props.data.topics || (await fetchData()) || []) const topics = createAsync<Topic[]>(async () => props.data.topics || (await fetchData()) || [])
const [topicsSort, setTopicsSort] = createSignal<string>('shouts') const { addTopics } = useTopics()
const [searchParams] = useSearchParams<{ by?: string }>() createEffect(() => addTopics(topics()||[]))
createEffect(on(() => searchParams?.by || 'shouts', setTopicsSort))
return ( return (
<PageLayout withPadding={true} title={`${t('Discours')}:${t('All topics')}`}> <PageLayout withPadding={true} title={`${t('Discours')}:${t('All topics')}`}>
<ReactionsProvider> <ReactionsProvider>
<Suspense fallback={<Loading />}> <Suspense fallback={<Loading />}>
<AllTopics topics={topics()?.sort(byTopicStatDesc(topicsSort() as keyof TopicStat)) || []} /> <AllTopics topics={topics() || []} />
</Suspense> </Suspense>
</ReactionsProvider> </ReactionsProvider>
</PageLayout> </PageLayout>