meta-refactored

This commit is contained in:
Untone 2024-07-09 12:13:13 +03:00
parent b204204a31
commit e3ac3cc406
31 changed files with 329 additions and 487 deletions

View File

@ -1,6 +1,6 @@
// import { install } from 'ga-gtag'
import { createPopper } from '@popperjs/core'
import { Link, Meta } from '@solidjs/meta'
import { Link } from '@solidjs/meta'
import { A, useSearchParams } from '@solidjs/router'
import { clsx } from 'clsx'
import { For, Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js'
@ -12,10 +12,9 @@ import { useSession } from '~/context/session'
import { DEFAULT_HEADER_OFFSET, useUI } from '~/context/ui'
import type { Author, Maybe, QueryLoad_Reactions_ByArgs, Shout, Topic } from '~/graphql/schema/core.gen'
import { isCyrillic } from '~/intl/translate'
import { getImageUrl, getOpenGraphImageUrl } from '~/lib/getImageUrl'
import { getImageUrl } from '~/lib/getImageUrl'
import { MediaItem } from '~/types/mediaitem'
import { capitalize } from '~/utils/capitalize'
import { getArticleDescription, getArticleKeywords } from '~/utils/meta'
import { AuthorBadge } from '../Author/AuthorBadge'
import { CardTopic } from '../Feed/CardTopic'
import { FeedArticlePopup } from '../Feed/FeedArticlePopup'
@ -302,7 +301,6 @@ export const FullArticle = (props: Props) => {
)
onMount(async () => {
// install('G-LQ4B87H8C2')
const opts: QueryLoad_Reactions_ByArgs = { by: { shout: props.article.slug }, limit: 999, offset: 0 }
const _rrr = await loadReactionsBy(opts)
addSeen(props.article.slug)
@ -326,34 +324,11 @@ export const FullArticle = (props: Props) => {
})
})
const cover = props.article.cover || 'production/image/logo_image.png'
const ogImage = getOpenGraphImageUrl(cover, {
title: props.article.title,
topic: mainTopic()?.title || '',
author: props.article.authors?.[0]?.name || '',
width: 1200
})
const description = getArticleDescription(props.article.description || body() || media()[0]?.body)
const ogTitle = props.article.title
const keywords = getArticleKeywords(props.article)
const shareUrl = getShareUrl({ pathname: `/${props.article.slug || ''}` })
const getAuthorName = (a: Author) => {
return lang() === 'en' && isCyrillic(a.name || '') ? capitalize(a.slug.replace(/-/, ' ')) : a.name
}
const getAuthorName = (a: Author) =>
lang() === 'en' && isCyrillic(a.name || '') ? capitalize(a.slug.replace(/-/, ' ')) : a.name
return (
<>
<Meta name="descprition" content={description} />
<Meta name="keywords" content={keywords} />
<Meta name="og:type" content="article" />
<Meta name="og:title" content={ogTitle} />
<Meta name="og:image" content={ogImage} />
<Meta name="og:description" content={description} />
<Meta name="twitter:card" content="summary_large_image" />
<Meta name="twitter:title" content={ogTitle} />
<Meta name="twitter:description" content={description} />
<Meta name="twitter:image" content={ogImage} />
<For each={imageUrls()}>{(imageUrl) => <Link rel="preload" as="image" href={imageUrl} />}</For>
<div class="wide-container">
<div class="row position-relative">
@ -522,7 +497,7 @@ export const FullArticle = (props: Props) => {
<div class={styles.shoutStatsItem} ref={triggerRef}>
<SharePopup
title={props.article.title}
description={description}
description={props.article.description || body() || media()[0]?.body}
imageUrl={props.article.cover || ''}
shareUrl={shareUrl}
containerCssClass={stylesHeader.control}
@ -623,7 +598,7 @@ export const FullArticle = (props: Props) => {
</Modal>
<ShareModal
title={props.article.title}
description={description}
description={props.article.description || body() || media()[0]?.body}
imageUrl={props.article.cover || ''}
shareUrl={shareUrl}
/>

View File

@ -1,99 +0,0 @@
import { clsx } from 'clsx'
import { For, Show, createEffect, createSignal, on } from 'solid-js'
import { useAuthors } from '~/context/authors'
import { useLocalize } from '~/context/localize'
import { loadAuthors } from '~/graphql/api/public'
import { Author } from '~/graphql/schema/core.gen'
import { AuthorBadge } from '../Author/AuthorBadge'
import { InlineLoader } from '../InlineLoader'
import { AUTHORS_PER_PAGE } from '../Views/AllAuthors/AllAuthors'
import { Button } from '../_shared/Button'
import styles from './AuthorsList.module.scss'
type Props = {
class?: string
query: 'followers' | 'shouts'
searchQuery?: string
allAuthorsLength?: number
}
// pagination handling, loadAuthors cached from api, addAuthors to context
export const AuthorsList = (props: Props) => {
const { t } = useLocalize()
const { addAuthors } = useAuthors()
const [authorsByShouts, setAuthorsByShouts] = createSignal<Author[]>()
const [authorsByFollowers, setAuthorsByFollowers] = createSignal<Author[]>()
const [loading, setLoading] = createSignal(false)
const [currentPage, setCurrentPage] = createSignal({ shouts: 0, followers: 0 })
const [allLoaded, setAllLoaded] = createSignal(false)
const fetchAuthors = async (queryType: Props['query'], page: number) => {
setLoading(true)
const offset = AUTHORS_PER_PAGE * page
const fetcher = await loadAuthors({
by: { order: queryType },
limit: AUTHORS_PER_PAGE,
offset
})
const result = await fetcher()
if (result) {
addAuthors([...result])
if (queryType === 'shouts') {
setAuthorsByShouts((prev) => [...(prev || []), ...result])
} else if (queryType === 'followers') {
setAuthorsByFollowers((prev) => [...(prev || []), ...result])
}
setLoading(false)
}
}
const loadMoreAuthors = () => {
const nextPage = currentPage()[props.query] + 1
fetchAuthors(props.query, nextPage).then(() =>
setCurrentPage({ ...currentPage(), [props.query]: nextPage })
)
}
createEffect(
on(
() => props.query,
(query) => {
const al = query === 'shouts' ? authorsByShouts() : authorsByFollowers()
if (al?.length === 0 && currentPage()[query] === 0) {
setCurrentPage((prev) => ({ ...prev, [query]: 0 }))
fetchAuthors(query, 0).then(() => setCurrentPage((prev) => ({ ...prev, [query]: 1 })))
}
}
)
)
const authorsList = () => (props.query === 'shouts' ? authorsByShouts() : authorsByFollowers())
createEffect(() => setAllLoaded(props.allAuthorsLength === authorsList.length))
return (
<div class={clsx(styles.AuthorsList, props.class)}>
<For each={authorsList()}>
{(author) => (
<div class="row">
<div class="col-lg-20 col-xl-18">
<AuthorBadge author={author} />
</div>
</div>
)}
</For>
<div class="row">
<div class="col-lg-20 col-xl-18">
<div class={styles.action}>
<Show when={!loading() && (authorsList()?.length || 0) > 0 && !allLoaded()}>
<Button value={t('Load more')} onClick={loadMoreAuthors} />
</Show>
<Show when={loading() && !allLoaded()}>
<InlineLoader />
</Show>
</div>
</div>
</div>
</div>
)
}

View File

@ -1 +0,0 @@
export { AuthorsList } from './AuthorsList'

View File

@ -7,7 +7,6 @@ import { useTopics } from '~/context/topics'
import { useUI } from '~/context/ui'
import type { Topic } from '../../../graphql/schema/core.gen'
import { getRandomTopicsFromArray } from '../../../lib/getRandomTopicsFromArray'
import { getArticleDescription } from '../../../utils/meta'
import { SharePopup, getShareUrl } from '../../Article/SharePopup'
import { Icon } from '../../_shared/Icon'
import { Newsletter } from '../../_shared/Newsletter'
@ -24,7 +23,7 @@ type Props = {
title?: string
slug?: string
isHeaderFixed?: boolean
articleBody?: string
desc?: string
cover?: string
scrollToComments?: (value: boolean) => void
}
@ -324,10 +323,8 @@ export const Header = (props: Props) => {
title={props.title || ''}
imageUrl={props.cover || ''}
shareUrl={getShareUrl()}
description={getArticleDescription(props.articleBody?.slice(0, 100) || '')}
onVisibilityChange={(isVisible) => {
setIsSharePopupVisible(isVisible)
}}
description={props.desc || ''}
onVisibilityChange={setIsSharePopupVisible}
containerCssClass={styles.control}
trigger={
<>

View File

@ -1 +0,0 @@
export { Topics } from './Topics'

View File

@ -3,9 +3,9 @@ import { Icon } from '~/components/_shared/Icon'
import { useLocalize } from '~/context/localize'
import { A, useMatch } from '@solidjs/router'
import styles from './Topics.module.scss'
import styles from './TopicsNav.module.scss'
export const Topics = () => {
export const TopicsNav = () => {
const { t } = useLocalize()
const matchExpo = useMatch(() => '/expo')
return (

View File

@ -0,0 +1 @@
export { TopicsNav } from './TopicsNav'

View File

@ -1,7 +1,7 @@
import type { Author, Topic } from '~/graphql/schema/core.gen'
import { clsx } from 'clsx'
import { Show, createEffect, createSignal } from 'solid-js'
import { Show, createEffect, createMemo, createSignal } from 'solid-js'
import { useFollowing } from '~/context/following'
import { useLocalize } from '~/context/localize'
@ -9,6 +9,7 @@ import { useSession } from '~/context/session'
import { FollowingEntity } from '~/graphql/schema/core.gen'
import { Button } from '../_shared/Button'
import { capitalize } from '~/utils/capitalize'
import { FollowingCounters } from '../_shared/FollowingCounters/FollowingCounters'
import { Icon } from '../_shared/Icon'
import styles from './Full.module.scss'
@ -20,11 +21,21 @@ type Props = {
}
export const FullTopic = (props: Props) => {
const { t } = useLocalize()
const { t, lang } = useLocalize()
const { follows, changeFollowing } = useFollowing()
const { requireAuthentication } = useSession()
const [followed, setFollowed] = createSignal()
const title = createMemo(
() =>
// FIXME: use title translation
`#${capitalize(
lang() === 'en'
? props.topic.slug.replace(/-/, ' ')
: props.topic.title || props.topic.slug.replace(/-/, ' '),
true
)}`
)
createEffect(() => {
if (follows?.topics?.length !== 0) {
const items = follows.topics || []
@ -42,7 +53,7 @@ export const FullTopic = (props: Props) => {
return (
<div class={clsx(styles.topicHeader, 'col-md-16 col-lg-12 offset-md-4 offset-lg-6')}>
<h1>#{props.topic?.title}</h1>
<h1>{title()}</h1>
<p class={styles.topicDescription} innerHTML={props.topic?.body || ''} />
<div class={styles.topicDetails}>

View File

@ -1,25 +1,24 @@
import { Meta } from '@solidjs/meta'
import { useSearchParams } from '@solidjs/router'
import { clsx } from 'clsx'
import { For, Show, createEffect, createMemo, createSignal, on, onMount } from 'solid-js'
import { AuthorBadge } from '~/components/Author/AuthorBadge'
import { InlineLoader } from '~/components/InlineLoader'
import { Button } from '~/components/_shared/Button'
import { Loading } from '~/components/_shared/Loading'
import { SearchField } from '~/components/_shared/SearchField'
import { useAuthors } from '~/context/authors'
import { useLocalize } from '~/context/localize'
import type { Author } from '~/graphql/schema/core.gen'
import enKeywords from '~/intl/locales/en/keywords.json'
import ruKeywords from '~/intl/locales/ru/keywords.json'
import { authorLetterReduce, translateAuthor } from '~/intl/translate'
import { dummyFilter } from '~/lib/dummyFilter'
import { getImageUrl } from '~/lib/getImageUrl'
import { scrollHandler } from '~/utils/scroll'
import { AuthorsList } from '../../AuthorsList'
import styles from './AllAuthors.module.scss'
import stylesAuthorList from './AuthorsList.module.scss'
type Props = {
authors: Author[]
topFollowedAuthors?: Author[]
topWritingAuthors?: Author[]
authorsByFollowers?: Author[]
authorsByShouts?: Author[]
isLoaded: boolean
}
export const AUTHORS_PER_PAGE = 20
@ -34,8 +33,9 @@ export const AllAuthors = (props: Props) => {
const { t, lang } = useLocalize()
const alphabet = createMemo(() => ABC[lang()] || ABC['ru'])
const [searchParams, changeSearchParams] = useSearchParams<{ by?: string }>()
const { authorsSorted, setAuthorsSort } = useAuthors()
const { authorsSorted, setAuthorsSort, loadAuthors } = useAuthors()
const authors = createMemo(() => props.authors || authorsSorted())
const [loading, setLoading] = createSignal<boolean>(false)
// filter
const [searchQuery, setSearchQuery] = createSignal('')
@ -52,7 +52,8 @@ export const AllAuthors = (props: Props) => {
// store by first char
const byLetterFiltered = createMemo<{ [letter: string]: Author[] }>(() => {
console.debug('[components.AllAuthors] byLetterFiltered')
if (!(filteredAuthors()?.length > 0)) return {}
console.debug('[components.AllAuthors] update byLetterFiltered', filteredAuthors()?.length)
return (
filteredAuthors()?.reduce(
(acc, author: Author) => authorLetterReduce(acc, author, lang()),
@ -69,24 +70,35 @@ export const AllAuthors = (props: Props) => {
return keys
})
const ogImage = createMemo(() => getImageUrl('production/image/logo_image.png'))
const ogTitle = createMemo(() => t('Authors'))
const description = createMemo(() => t('List of authors of the open editorial community'))
const fetchAuthors = async (queryType: string, page: number) => {
try {
console.debug('[components.AuthorsList] fetching authors...')
setLoading(true)
setAuthorsSort?.(queryType)
const offset = AUTHORS_PER_PAGE * page
await loadAuthors({
by: { order: queryType },
limit: AUTHORS_PER_PAGE,
offset
})
} catch (error) {
console.error('[components.AuthorsList] error fetching authors:', error)
} finally {
setLoading(false)
}
}
const [currentPage, setCurrentPage] = createSignal<{ followers: number; shouts: number }>({
followers: 0,
shouts: 0
})
const loadMoreAuthors = () => {
const by = searchParams?.by as 'followers' | 'shouts' | undefined
if (!by) return
const nextPage = currentPage()[by] + 1
fetchAuthors(by, nextPage).then(() => setCurrentPage({ ...currentPage(), [by]: nextPage }))
}
return (
<div class={clsx([styles.allAuthorsPage, 'wide-container'])}>
<Meta name="descprition" content={description() || ''} />
<Meta name="keywords" content={lang() === 'ru' ? ruKeywords[''] : enKeywords['']} />
<Meta name="og:type" content="article" />
<Meta name="og:title" content={ogTitle() || ''} />
<Meta name="og:image" content={ogImage() || ''} />
<Meta name="twitter:image" content={ogImage() || ''} />
<Meta name="og:description" content={description() || ''} />
<Meta name="twitter:card" content="summary_large_image" />
<Meta name="twitter:title" content={ogTitle() || ''} />
<Meta name="twitter:description" content={description() || ''} />
<Show when={props.isLoaded} fallback={<Loading />}>
<div class="offset-md-5">
const TabNavigator = () => (
<div class="row">
<div class="col-lg-20 col-xl-18">
<h1>{t('Authors')}</h1>
@ -121,8 +133,9 @@ export const AllAuthors = (props: Props) => {
</ul>
</div>
</div>
)
<Show when={searchParams?.by === 'name'}>
const AbcNavigator = () => (
<div class="row">
<div class="col-lg-20 col-xl-18">
<ul class={clsx('nodash', styles.alphabet)}>
@ -146,6 +159,9 @@ export const AllAuthors = (props: Props) => {
</ul>
</div>
</div>
)
const AbcAuthorsList = () => (
<For each={sortedKeys() || []}>
{(letter) => (
<div class={clsx(styles.group, 'group')}>
@ -173,16 +189,45 @@ export const AllAuthors = (props: Props) => {
</div>
)}
</For>
)
const AuthorsSortedList = () => (
<div class={clsx(stylesAuthorList.AuthorsList)}>
<For each={authorsSorted?.()}>
{(author) => (
<div class="row">
<div class="col-lg-20 col-xl-18">
<AuthorBadge author={author} />
</div>
</div>
)}
</For>
<div class="row">
<div class="col-lg-20 col-xl-18">
<div class={stylesAuthorList.action}>
<Show when={!loading() && ((authorsSorted?.() || []).length || 0) > 0}>
<Button value={t('Load more')} onClick={loadMoreAuthors} aria-live="polite" />
</Show>
<Show when={authors().length && searchParams?.by !== 'name' && props.isLoaded}>
<AuthorsList
allAuthorsLength={authors().length}
searchQuery={searchQuery()}
query={searchParams?.by === 'followers' ? 'followers' : 'shouts'}
/>
<Show when={loading()}>
<InlineLoader />
</Show>
</div>
</Show>
</div>
</div>
</div>
)
return (
<>
<Show when={props.isLoaded} fallback={<Loading />}>
<div class="offset-md-5">
<TabNavigator />
<Show when={searchParams?.by === 'name'} fallback={<AuthorsSortedList />}>
<AbcNavigator />
<AbcAuthorsList />
</Show>
</div>
</Show>
</>
)
}

View File

@ -1,4 +1,3 @@
import { Meta } from '@solidjs/meta'
import { A, useSearchParams } from '@solidjs/router'
import { clsx } from 'clsx'
import { For, Show, createEffect, createMemo, createSignal, on, onMount } from 'solid-js'
@ -7,10 +6,7 @@ import { SearchField } from '~/components/_shared/SearchField'
import { useLocalize } from '~/context/localize'
import { useTopics } from '~/context/topics'
import type { Topic } from '~/graphql/schema/core.gen'
import enKeywords from '~/intl/locales/en/keywords.json'
import ruKeywords from '~/intl/locales/ru/keywords.json'
import { dummyFilter } from '~/lib/dummyFilter'
import { getImageUrl } from '~/lib/getImageUrl'
import { capitalize } from '~/utils/capitalize'
import { scrollHandler } from '~/utils/scroll'
import { TopicBadge } from '../../Topic/TopicBadge'
@ -98,26 +94,9 @@ export const AllTopics = (props: Props) => {
</div>
</div>
)
// meta
const ogImage = getImageUrl('production/image/logo_image.png')
const ogTitle = t('Themes and plots')
const description = t(
'Thematic table of contents of the magazine. Here you can find all the topics that the community authors wrote about'
)
return (
<div class={clsx(styles.allTopicsPage, 'wide-container')}>
<Meta name="descprition" content={description} />
<Meta name="keywords" content={lang() === 'ru' ? ruKeywords[''] : enKeywords['']} />
<Meta name="og:type" content="article" />
<Meta name="og:title" content={ogTitle} />
<Meta name="og:image" content={ogImage} />
<Meta name="twitter:image" content={ogImage} />
<Meta name="og:description" content={description} />
<Meta name="twitter:card" content="summary_large_image" />
<Meta name="twitter:title" content={ogTitle} />
<Meta name="twitter:description" content={description} />
<>
<h1>{t('Themes and plots')}</h1>
<Show when={Boolean(filteredResults())} fallback={<Loading />}>
<div class="row">
<div class="col-md-19 offset-md-5">
@ -199,6 +178,6 @@ export const AllTopics = (props: Props) => {
</div>
</div>
</Show>
</div>
</>
)
}

View File

@ -1,4 +1,3 @@
import { Meta, Title } from '@solidjs/meta'
import { A, useLocation, useParams } from '@solidjs/router'
import { clsx } from 'clsx'
import { For, Match, Show, Switch, createEffect, createMemo, createSignal, on, onMount } from 'solid-js'
@ -10,14 +9,12 @@ import { useGraphQL } from '~/context/graphql'
import { useLocalize } from '~/context/localize'
import { useSession } from '~/context/session'
import { useUI } from '~/context/ui'
import { loadReactions } from '~/graphql/api/public'
import loadShoutsQuery from '~/graphql/query/core/articles-load-by'
import getAuthorFollowersQuery from '~/graphql/query/core/author-followers'
import getAuthorFollowsQuery from '~/graphql/query/core/author-follows'
import loadReactionsBy from '~/graphql/query/core/reactions-load-by'
import type { Author, Reaction, Shout, Topic } from '~/graphql/schema/core.gen'
import { getImageUrl } from '~/lib/getImageUrl'
import { byCreated } from '~/lib/sortby'
import { getArticleDescription } from '~/utils/meta'
import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll'
import { splitToPages } from '~/utils/splitToPages'
import stylesArticle from '../../Article/Article.module.scss'
@ -57,7 +54,7 @@ export const AuthorView = (props: Props) => {
const [followers, setFollowers] = createSignal<Author[]>([] as Author[])
const [following, changeFollowing] = createSignal<Array<Author | Topic>>([] as Array<Author | Topic>) // flat AuthorFollowsResult
const [showExpandBioControl, setShowExpandBioControl] = createSignal(false)
const [commented, setCommented] = createSignal<Reaction[]>()
const [commented, setCommented] = createSignal<Reaction[]>([])
const { query } = useGraphQL()
// пагинация загрузки ленты постов
@ -123,11 +120,11 @@ export const AuthorView = (props: Props) => {
if (!commented() && profile) {
await loadMore()
const resp = await query(loadReactionsBy, {
const commentsFetcher = loadReactions({
by: { comment: true, created_by: profile.id }
}).toPromise()
const ccc = resp?.data?.load_reactions_by
if (ccc) setCommented(ccc)
})
const ccc = await commentsFetcher()
if (ccc) setCommented((_) => ccc || [])
}
}
// { defer: true },
@ -150,31 +147,12 @@ export const AuthorView = (props: Props) => {
const pages = createMemo<Shout[][]>(() =>
splitToPages(sortedFeed(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
)
const ogImage = createMemo(() =>
author()?.pic
? getImageUrl(author()?.pic || '', { width: 1200 })
: getImageUrl('production/image/logo_image.png')
)
const description = createMemo(() => getArticleDescription(author()?.bio || ''))
const handleDeleteComment = (id: number) => {
setCommented((prev) => (prev || []).filter((comment) => comment.id !== id))
}
return (
<div class={styles.authorPage}>
<Show when={author()}>
<Title>{author()?.name}</Title>
<Meta name="descprition" content={description()} />
<Meta name="og:type" content="profile" />
<Meta name="og:title" content={author()?.name || ''} />
<Meta name="og:image" content={ogImage()} />
<Meta name="og:description" content={description()} />
<Meta name="twitter:card" content="summary_large_image" />
<Meta name="twitter:title" content={author()?.name || ''} />
<Meta name="twitter:description" content={description()} />
<Meta name="twitter:image" content={ogImage()} />
</Show>
<div class="wide-container">
<Show when={author()} fallback={<Loading />}>
<>

View File

@ -1,4 +1,3 @@
import { Meta } from '@solidjs/meta'
import { A, createAsync, useLocation, useNavigate, useSearchParams } from '@solidjs/router'
import { clsx } from 'clsx'
import { For, Show, createMemo, createSignal, onMount } from 'solid-js'
@ -17,9 +16,6 @@ import { useTopics } from '~/context/topics'
import { useUI } from '~/context/ui'
import { loadUnratedShouts } from '~/graphql/api/private'
import type { Author, Reaction, Shout } from '~/graphql/schema/core.gen'
import ruKeywords from '~/intl/locales/ru/keywords.json'
import enKeywords from '~/intl/locales/ru/keywords.json'
import { getImageUrl } from '~/lib/getImageUrl'
import { byCreated } from '~/lib/sortby'
import { FeedSearchParams } from '~/routes/feed/[feed]'
import { CommentDate } from '../../Article/CommentDate'
@ -41,7 +37,7 @@ export type FeedProps = {
}
export const FeedView = (props: FeedProps) => {
const { t, lang } = useLocalize()
const { t } = useLocalize()
const loc = useLocation()
const client = useGraphQL()
const unrated = createAsync(async () => {
@ -79,12 +75,6 @@ export const FeedView = (props: FeedProps) => {
})
})
const ogImage = getImageUrl('production/image/logo_image.png')
const description = createMemo(() =>
t('Independent media project about culture, science, art and society with horizontal editing')
)
const ogTitle = createMemo(() => t('Feed'))
const [shareData, setShareData] = createSignal<Shout | undefined>()
const handleShare = (shared: Shout | undefined) => {
showModal('share')
@ -92,17 +82,7 @@ export const FeedView = (props: FeedProps) => {
}
return (
<div class="wide-container feed">
<Meta name="descprition" content={description()} />
<Meta name="keywords" content={lang() === 'ru' ? ruKeywords[''] : enKeywords['']} />
<Meta name="og:type" content="article" />
<Meta name="og:title" content={ogTitle()} />
<Meta name="og:image" content={ogImage} />
<Meta name="twitter:image" content={ogImage} />
<Meta name="og:description" content={description()} />
<Meta name="twitter:card" content="summary_large_image" />
<Meta name="twitter:title" content={ogTitle()} />
<Meta name="twitter:description" content={description()} />
<div class="feed">
<div class="row">
<div class={clsx('col-md-5 col-xl-4', styles.feedNavigation)}>
<Sidebar />

View File

@ -1,13 +1,9 @@
import { For, Show, createEffect, createMemo, createSignal, on } from 'solid-js'
import { Meta } from '@solidjs/meta'
import { useAuthors } from '~/context/authors'
import { useLocalize } from '~/context/localize'
import { useTopics } from '~/context/topics'
import { loadShouts } from '~/graphql/api/public'
import { Author, Shout, Topic } from '~/graphql/schema/core.gen'
import enKeywords from '~/intl/locales/en/keywords.json'
import ruKeywords from '~/intl/locales/ru/keywords.json'
import { SHOUTS_PER_PAGE } from '~/routes/(home)'
import { capitalize } from '~/utils/capitalize'
import { splitToPages } from '~/utils/splitToPages'
@ -20,7 +16,7 @@ import { Row2 } from '../Feed/Row2'
import { Row3 } from '../Feed/Row3'
import { Row5 } from '../Feed/Row5'
import RowShort from '../Feed/RowShort'
import { Topics } from '../Nav/Topics'
import { TopicsNav } from '../Nav/TopicsNav'
import { Icon } from '../_shared/Icon'
import { ArticleCardSwiper } from '../_shared/SolidSwiper/ArticleCardSwiper'
import styles from './Home.module.scss'
@ -40,7 +36,7 @@ export interface HomeViewProps {
}
export const HomeView = (props: HomeViewProps) => {
const { t, lang } = useLocalize()
const { t } = useLocalize()
const { topAuthors, addAuthors } = useAuthors()
const { topTopics, randomTopic } = useTopics()
const [randomTopicArticles, setRandomTopicArticles] = createSignal<Shout[]>([])
@ -75,9 +71,8 @@ export const HomeView = (props: HomeViewProps) => {
return (
<>
<Meta name="keywords" content={`${lang() === 'ru' ? ruKeywords[''] : enKeywords['']}`} />
<Show when={(props.featuredShouts || []).length > 0}>
<Topics />
<TopicsNav />
<Row5 articles={props.featuredShouts.slice(0, 5)} nodate={true} />
<Hero />
<Show when={props.featuredShouts?.length > SHOUTS_PER_PAGE}>

View File

@ -1,10 +1,5 @@
import { Meta } from '@solidjs/meta'
import { JSX, createMemo, onMount } from 'solid-js'
import { useLocalize } from '~/context/localize'
import enKeywords from '~/intl/locales/en/keywords.json'
import ruKeywords from '~/intl/locales/ru/keywords.json'
import { JSX, onMount } from 'solid-js'
import { processPrepositions } from '~/intl/prepositions'
import { getImageUrl } from '~/lib/getImageUrl'
import { TableOfContents } from '../TableOfContents'
import { PageLayout } from '../_shared/PageLayout'
@ -15,39 +10,16 @@ type Props = {
}
export const StaticPage = (props: Props) => {
let articleBodyElement: HTMLElement | null = null
const { t, lang } = useLocalize()
const ogTitle = createMemo(() => t(props.title || 'Discours'))
const description = createMemo(() => t(props.desc || ''))
const ogImage = getImageUrl('production/image/logo_image.png')
const keywords = createMemo(() => {
const page = props.title.toLocaleLowerCase() as keyof typeof ruKeywords
return `${lang() === 'ru' ? ruKeywords[page] : enKeywords[page]}`
})
let bodyEl: HTMLDivElement | undefined
let bodyEl: HTMLElement | undefined
onMount(() => {
if (bodyEl) bodyEl.innerHTML = processPrepositions(bodyEl.innerHTML)
})
return (
<PageLayout title={props.title}>
<Meta name="descprition" content={description()} />
<Meta name="keywords" content={keywords()} />
<Meta name="og:type" content="article" />
<Meta name="og:title" content={ogTitle()} />
<Meta name="og:image" content={ogImage} />
<Meta name="twitter:image" content={ogImage} />
<Meta name="og:description" content={description()} />
<Meta name="twitter:card" content="summary_large_image" />
<Meta name="twitter:title" content={ogTitle()} />
<Meta name="twitter:description" content={description()} />
<article
class="wide-container container--static-page"
id="articleBody"
ref={(el) => (articleBodyElement = el)}
>
<PageLayout title={props.title} desc={props.desc} key={props.title.toLowerCase()}>
<article class="wide-container container--static-page" id="articleBody" ref={(el) => (bodyEl = el)}>
<div class="row">
<div class="col-md-12 col-xl-14 offset-md-5 order-md-first mt-5" ref={(el) => (bodyEl = el)}>
<h1>{ogTitle()}</h1>
<div class="col-md-12 col-xl-14 offset-md-5 order-md-first mt-5">
<h1>{props.title}</h1>
{props.children}
</div>
@ -55,7 +27,7 @@ export const StaticPage = (props: Props) => {
<TableOfContents
variant="article"
parentSelector="#articleBody"
body={(articleBodyElement as unknown as HTMLElement)?.outerHTML}
body={(bodyEl as unknown as HTMLElement)?.outerHTML}
/>
</div>
</div>

View File

@ -1,10 +1,6 @@
import { Author, AuthorsBy, LoadShoutsOptions, Shout, Topic } from '~/graphql/schema/core.gen'
import { Meta } from '@solidjs/meta'
import { useSearchParams } from '@solidjs/router'
import { clsx } from 'clsx'
import { For, Show, createEffect, createMemo, createSignal, on, onMount } from 'solid-js'
import { useSearchParams } from '@solidjs/router'
import { useAuthors } from '~/context/authors'
import { useFeed } from '~/context/feed'
import { useGraphQL } from '~/context/graphql'
@ -14,12 +10,8 @@ import getRandomTopShoutsQuery from '~/graphql/query/core/articles-load-random-t
import loadShoutsRandomQuery from '~/graphql/query/core/articles-load-random-topic'
import loadAuthorsByQuery from '~/graphql/query/core/authors-load-by'
import getTopicFollowersQuery from '~/graphql/query/core/topic-followers'
import enKeywords from '~/intl/locales/en/keywords.json'
import ruKeywords from '~/intl/locales/ru/keywords.json'
import { getImageUrl } from '~/lib/getImageUrl'
import { capitalize } from '~/utils/capitalize'
import { Author, AuthorsBy, LoadShoutsOptions, Shout, Topic } from '~/graphql/schema/core.gen'
import { getUnixtime } from '~/utils/getServerDate'
import { getArticleDescription } from '~/utils/meta'
import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll'
import { splitToPages } from '~/utils/splitToPages'
import styles from '../../styles/Topic.module.scss'
@ -45,7 +37,7 @@ export const PRERENDERED_ARTICLES_COUNT = 28
const LOAD_MORE_PAGE_SIZE = 9 // Row3 + Row3 + Row3
export const TopicView = (props: Props) => {
const { t, lang } = useLocalize()
const { t } = useLocalize()
const { query } = useGraphQL()
const [searchParams, changeSearchParams] = useSearchParams<TopicsPageSearchParams>()
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
@ -55,7 +47,6 @@ export const TopicView = (props: Props) => {
const { authorsByTopic } = useAuthors()
const [favoriteTopArticles, setFavoriteTopArticles] = createSignal<Shout[]>([])
const [reactedTopMonthArticles, setReactedTopMonthArticles] = createSignal<Shout[]>([])
const [topic, setTopic] = createSignal<Topic>()
createEffect(
on([() => props.topicSlug, topic, topicEntities], async ([slug, t, ttt]) => {
@ -113,16 +104,6 @@ export const TopicView = (props: Props) => {
}
}
const title = createMemo(
() =>
`#${capitalize(
lang() === 'en'
? (topic() as Topic)?.slug.replace(/-/, ' ')
: (topic() as Topic)?.title || (topic() as Topic)?.slug.replace(/-/, ' '),
true
)}`
)
const loadMore = async () => {
saveScrollPosition()
@ -154,31 +135,8 @@ export const TopicView = (props: Props) => {
const pages = createMemo<Shout[][]>(() =>
splitToPages(sortedFeed(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
)
const ogImage = () =>
topic()?.pic
? getImageUrl(topic()?.pic || '', { width: 1200 })
: getImageUrl('production/image/logo_image.png')
const description = () =>
topic()?.body
? getArticleDescription(topic()?.body || '')
: t('The most interesting publications on the topic', { topicName: title() })
return (
<div class={styles.topicPage}>
<Meta name="descprition" content={description()} />
<Meta
name="keywords"
content={`${title()}, ${lang() === 'ru' ? ruKeywords['topic'] : enKeywords['topic']}`}
/>
<Meta name="og:type" content="article" />
<Meta name="og:title" content={title()} />
<Meta name="og:image" content={ogImage()} />
<Meta name="twitter:image" content={ogImage()} />
<Meta name="og:description" content={description()} />
<Meta name="twitter:card" content="summary_large_image" />
<Meta name="twitter:title" content={title()} />
<Meta name="twitter:description" content={description()} />
<FullTopic topic={topic() as Topic} followers={followers()} authors={topicAuthors()} />
<div class="wide-container">
<div class={clsx(styles.groupControls, 'row group__controls')}>

View File

@ -1,20 +1,24 @@
import type { JSX } from 'solid-js'
import { Title } from '@solidjs/meta'
import { Meta, Title } from '@solidjs/meta'
import { useLocation } from '@solidjs/router'
import { clsx } from 'clsx'
import { Show, createEffect, createSignal } from 'solid-js'
import type { JSX } from 'solid-js'
import { Show, createEffect, createMemo, createSignal } from 'solid-js'
import { useLocalize } from '~/context/localize'
import { Shout } from '~/graphql/schema/core.gen'
import enKeywords from '~/intl/locales/en/keywords.json'
import ruKeywords from '~/intl/locales/ru/keywords.json'
import { getImageUrl, getOpenGraphImageUrl } from '~/lib/getImageUrl'
import { getArticleKeywords } from '~/utils/meta'
import { FooterView } from '../Discours/Footer'
import { Header } from '../Nav/Header'
import '../../styles/app.scss'
import styles from './PageLayout.module.scss'
type Props = {
type PageLayoutProps = {
title: string
desc?: string
headerTitle?: string
slug?: string
articleBody?: string
article?: Shout
cover?: string
children: JSX.Element
isHeaderFixed?: boolean
@ -23,29 +27,56 @@ type Props = {
withPadding?: boolean
zeroBottomPadding?: boolean
scrollToComments?: (value: boolean) => void
key?: string
}
export const PageLayout = (props: Props) => {
const isHeaderFixed = props.isHeaderFixed === undefined ? true : props.isHeaderFixed
const [scrollToComments, setScrollToComments] = createSignal<boolean>(false)
createEffect(() => {
if (props.scrollToComments) {
props.scrollToComments(scrollToComments())
}
export const PageLayout = (props: PageLayoutProps) => {
const isHeaderFixed = props.isHeaderFixed === undefined ? true : props.isHeaderFixed // FIXME: выглядит как костылек
const loc = useLocation()
const { t, lang } = useLocalize()
const imageUrl = props.cover ? getImageUrl(props.cover) : 'production/image/logo_image.png'
const ogImage = createMemo(() =>
// NOTE: preview generation logic works only for one article view
props.article
? getOpenGraphImageUrl(imageUrl, {
title: props.title,
topic: props.article?.topics?.[0]?.title || '',
author: props.article?.authors?.[0]?.name || '',
width: 1200
})
: imageUrl
)
const ogTitle = createMemo(() => t(props.title))
const description = createMemo(() => (props.desc ? t(props.desc) : ''))
const keypath = createMemo(() => (props.key || loc?.pathname.split('/')[0]) as keyof typeof ruKeywords)
const keywords = createMemo(
() =>
(props.article && getArticleKeywords(props.article as Shout)) ||
(lang() === 'ru' ? ruKeywords[keypath()] : enKeywords[keypath()])
)
const [scrollToComments, setScrollToComments] = createSignal<boolean>(false)
createEffect(() => props.scrollToComments?.(scrollToComments()))
return (
<>
<Title>{props.title}</Title>
<Header
slug={props.slug}
title={props.headerTitle}
articleBody={props.articleBody}
cover={props.articleBody}
desc={props.desc}
cover={imageUrl}
isHeaderFixed={isHeaderFixed}
scrollToComments={(value) => setScrollToComments(value)}
/>
<Meta name="descprition" content={description() || ''} />
<Meta name="keywords" content={keywords()} />
<Meta name="og:type" content="article" />
<Meta name="og:title" content={ogTitle() || ''} />
<Meta name="og:image" content={ogImage() || ''} />
<Meta name="twitter:image" content={ogImage() || ''} />
<Meta name="og:description" content={description() || ''} />
<Meta name="twitter:card" content="summary_large_image" />
<Meta name="twitter:title" content={ogTitle() || ''} />
<Meta name="twitter:description" content={description() || ''} />
<main
class={clsx('main-content', {
[styles.withPadding]: props.withPadding,
@ -53,7 +84,7 @@ export const PageLayout = (props: Props) => {
})}
classList={{ 'main-content--no-padding': !isHeaderFixed }}
>
{props.children}
<div class={clsx([props.class, 'wide-container'])}>{props.children}</div>
</main>
<Show when={props.hideFooter !== true}>
<FooterView />

View File

@ -40,7 +40,7 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
const { mutation } = useGraphQL()
const loadReactionsBy = async (opts: QueryLoad_Reactions_ByArgs): Promise<Reaction[]> => {
const fetcher = await loadReactions({ ...opts })
const fetcher = await loadReactions(opts)
const result = (await fetcher()) || []
const newReactionsByShout: Record<string, Reaction[]> = {}
const newReactionEntities = result.reduce(

View File

@ -60,7 +60,7 @@ export const loadReactions = (options: QueryLoad_Reactions_ByArgs) => {
const filter = new URLSearchParams(options.by as Record<string, string>)
console.debug(options)
return cache(async () => {
const resp = await defaultClient.query(loadReactionsByQuery, { ...options }).toPromise()
const resp = await defaultClient.query(loadReactionsByQuery, options).toPromise()
const result = resp?.data?.load_reactions_by
if (result) return result as Reaction[]
}, `reactions-${filter}-${page}`)

View File

@ -1,7 +1,7 @@
{
"dogma": "Discours.io, dogma, editorial principles, code of ethics, journalism, community",
"guide": "discours.io, guide, help, how to start, reference, tutorial",
"": "Discours.io, Discours magazine, Discours, culture, science, art, society, independent journalism, literature, music, cinema, video, photography",
"home": "Discours.io, Discours magazine, Discours, culture, science, art, society, independent journalism, literature, music, cinema, video, photography",
"principles": "Discours.io, communities, values, editorial rules, polyphony, creation",
"terms-of-use": "Discours.io, site rules, terms of use",
"topic": "{topic}, Discours.io, articles, journalism, research"

View File

@ -1,7 +1,7 @@
{
"dogma": "discours.io, догма, принципы редактирования, этический кодекс, журналистика, сообщество",
"guide": "discours.io, гид, помощь, как начать, справочник, туториал",
"": "discours.io, Дискурс журнал, Дискурс, культура, наука, искусство, общество, независимая журналистика, литература, музыка, кино, видео, фотография",
"home": "discours.io, Дискурс журнал, Дискурс, культура, наука, искусство, общество, независимая журналистика, литература, музыка, кино, видео, фотография",
"principles": "discours.io, сообщества, ценности, принципы редактировани, плюрализм мнений, сотворчество",
"terms-of-use": "discours.io, правила сайта, правила, пользовательское соглашение",
"topic": "discours.io, Дискурс, статьи, журналистика, исследование"

View File

@ -114,7 +114,7 @@ export default function HomePage(props: RouteSectionProps<HomeViewProps>) {
onMount(async () => await loadMoreFeatured())
return (
<PageLayout withPadding={true} title={t('Discours')}>
<PageLayout withPadding={true} title={t('Discours')} key={'home'}>
<ReactionsProvider>
<Suspense fallback={<Loading />}>
<HomeView {...(data() as HomeViewProps)} />

View File

@ -15,6 +15,7 @@ import { useLocalize } from '~/context/localize'
import { getShout } from '~/graphql/api/public'
import type { Shout } from '~/graphql/schema/core.gen'
import { initGA, loadGAScript } from '~/utils/ga'
import { getArticleKeywords } from '~/utils/meta'
import { FullArticle } from '../components/Article/FullArticle'
import { PageLayout } from '../components/_shared/PageLayout'
import { ReactionsProvider } from '../context/reactions'
@ -91,9 +92,9 @@ export default (props: RouteSectionProps<{ article: Shout }>) => {
<Show when={article()?.id} fallback={<Loading />}>
<PageLayout
title={title()}
desc={getArticleKeywords(article() as Shout)}
headerTitle={article()?.title || ''}
slug={article()?.slug}
articleBody={article()?.body}
cover={article()?.cover || ''}
scrollToComments={(value) => setScrollToComments(value)}
>

View File

@ -2,6 +2,7 @@ import { RouteDefinition, RouteLoadFuncArgs, type RouteSectionProps, createAsync
import { Suspense, createEffect, on } from 'solid-js'
import { AllAuthors } from '~/components/Views/AllAuthors'
import { AUTHORS_PER_PAGE } from '~/components/Views/AllAuthors/AllAuthors'
import styles from '~/components/Views/AllAuthors/AllAuthors.module.scss'
import { Loading } from '~/components/_shared/Loading'
import { PageLayout } from '~/components/_shared/PageLayout'
import { useAuthors } from '~/context/authors'
@ -27,13 +28,13 @@ export const route = {
const isAll = !by || by === 'name'
return {
authors: isAll && (await fetchAllAuthors()),
topFollowedAuthors: await fetchAuthorsWithStat(10, 'followers'),
topShoutsAuthors: await fetchAuthorsWithStat(10, 'shouts')
authorsByFollowers: await fetchAuthorsWithStat(10, 'followers'),
authorsByShouts: await fetchAuthorsWithStat(10, 'shouts')
} as AllAuthorsData
}
} satisfies RouteDefinition
type AllAuthorsData = { authors: Author[]; topFollowedAuthors: Author[]; topShoutsAuthors: Author[] }
type AllAuthorsData = { authors: Author[]; authorsByFollowers: Author[]; authorsByShouts: Author[] }
// addAuthors to context
@ -46,8 +47,8 @@ export default function AllAuthorsPage(props: RouteSectionProps<AllAuthorsData>)
if (props.data) return props.data
return {
authors: await fetchAllAuthors(),
topFollowedAuthors: await fetchAuthorsWithStat(10, 'followers'),
topShoutsAuthors: await fetchAuthorsWithStat(10, 'shouts')
authorsByFollowers: await fetchAuthorsWithStat(10, 'followers'),
authorsByShouts: await fetchAuthorsWithStat(10, 'shouts')
} as AllAuthorsData
})
@ -58,8 +59,8 @@ export default function AllAuthorsPage(props: RouteSectionProps<AllAuthorsData>)
([data, aa]) => {
if (data && aa) {
aa(data.authors as Author[])
aa(data.topFollowedAuthors as Author[])
aa(data.topShoutsAuthors as Author[])
aa(data.authorsByFollowers as Author[])
aa(data.authorsByShouts as Author[])
console.debug('[routes.author] added all authors:', data.authors)
}
},
@ -68,14 +69,19 @@ export default function AllAuthorsPage(props: RouteSectionProps<AllAuthorsData>)
)
return (
<PageLayout withPadding={true} title={`${t('Discours')} :: ${t('All authors')}`}>
<PageLayout
withPadding={true}
title={`${t('Discours')} :: ${t('All authors')}`}
class={styles.allAuthorsPage}
desc="List of authors of the open editorial community"
>
<ReactionsProvider>
<Suspense fallback={<Loading />}>
<AllAuthors
isLoaded={Boolean(data()?.authors)}
authors={data()?.authors || []}
topFollowedAuthors={data()?.topFollowedAuthors}
topWritingAuthors={data()?.topShoutsAuthors}
authorsByFollowers={data()?.authorsByFollowers}
authorsByShouts={data()?.authorsByShouts}
/>
</Suspense>
</ReactionsProvider>

View File

@ -9,6 +9,7 @@ import { useLocalize } from '~/context/localize'
import { ReactionsProvider } from '~/context/reactions'
import { loadShouts } from '~/graphql/api/public'
import { Author, LoadShoutsOptions, Shout } from '~/graphql/schema/core.gen'
import { getImageUrl } from '~/lib/getImageUrl'
import { SHOUTS_PER_PAGE } from '../../(home)'
const fetchAuthorShouts = async (slug: string, offset?: number) => {
@ -47,6 +48,12 @@ export default (props: RouteSectionProps<{ articles: Shout[] }>) => {
})
}
})
const cover = createMemo(() =>
author()?.pic
? getImageUrl(author()?.pic || '', { width: 1200 })
: getImageUrl('production/image/logo_image.png')
)
return (
<ErrorBoundary fallback={(_err) => <FourOuFourView />}>
<Suspense fallback={<Loading />}>
@ -54,8 +61,8 @@ export default (props: RouteSectionProps<{ articles: Shout[] }>) => {
title={`${t('Discours')} :: ${title()}`}
headerTitle={author()?.name || ''}
slug={author()?.slug}
articleBody={author()?.about || author()?.bio || ''}
cover={author()?.pic || ''}
desc={author()?.about || author()?.bio || ''}
cover={cover()}
>
<ReactionsProvider>
<AuthorView

View File

@ -1,7 +1,5 @@
import { Meta } from '@solidjs/meta'
import { useNavigate } from '@solidjs/router'
import { clsx } from 'clsx'
import { createMemo } from 'solid-js'
import { AuthGuard } from '~/components/AuthGuard'
import { Button } from '~/components/_shared/Button'
import { Icon } from '~/components/_shared/Icon'
@ -9,19 +7,11 @@ import { PageLayout } from '~/components/_shared/PageLayout'
import { useGraphQL } from '~/context/graphql'
import { useLocalize } from '~/context/localize'
import createShoutMutation from '~/graphql/mutation/core/article-create'
import enKeywords from '~/intl/locales/en/keywords.json'
import ruKeywords from '~/intl/locales/ru/keywords.json'
import { getImageUrl } from '~/lib/getImageUrl'
import styles from '~/styles/Create.module.scss'
import { LayoutType } from '~/types/common'
export default () => {
const { t, lang } = useLocalize()
const ogImage = getImageUrl('production/image/logo_image.png')
const ogTitle = createMemo(() => t('Choose a post type'))
const description = createMemo(() =>
t('Participate in the Discours: share information, join the editorial team')
)
const { t } = useLocalize()
const client = useGraphQL()
const navigate = useNavigate()
const handleCreate = async (layout: LayoutType) => {
@ -32,17 +22,11 @@ export default () => {
}
}
return (
<PageLayout title={`${t('Discours')} :: ${ogTitle()}`}>
<Meta name="descprition" content={description()} />
<Meta name="keywords" content={lang() === 'ru' ? ruKeywords[''] : enKeywords['']} />
<Meta name="og:type" content="article" />
<Meta name="og:title" content={ogTitle()} />
<Meta name="og:image" content={ogImage} />
<Meta name="twitter:image" content={ogImage} />
<Meta name="og:description" content={description()} />
<Meta name="twitter:card" content="summary_large_image" />
<Meta name="twitter:title" content={ogTitle()} />
<Meta name="twitter:description" content={description()} />
<PageLayout
title={`${t('Discours')} :: ${t('Choose a post type')}`}
key="home"
desc="Participate in the Discours: share information, join the editorial team"
>
<AuthGuard>
<article class={clsx('wide-container', 'container--static-page', styles.Create)}>
<h1>{t('Choose a post type')}</h1>

View File

@ -1,6 +1,6 @@
import { Params, RouteSectionProps, createAsync, useParams } from '@solidjs/router'
import { createEffect, createMemo, on } from 'solid-js'
import { Topics } from '~/components/Nav/Topics'
import { TopicsNav } from '~/components/Nav/TopicsNav'
import { Expo } from '~/components/Views/Expo'
import { PageLayout } from '~/components/_shared/PageLayout'
import { useLocalize } from '~/context/localize'
@ -61,7 +61,7 @@ export default (props: RouteSectionProps<Shout[]>) => {
return (
<PageLayout withPadding={true} zeroBottomPadding={true} title={`${t('Discours')} :: ${title()}`}>
<Topics />
<TopicsNav />
<Expo shouts={shouts() || []} layout={layout() as LayoutType} />
</PageLayout>
)

View File

@ -125,7 +125,12 @@ export default (props: RouteSectionProps<Shout[]>) => {
}
createEffect(() => setIsLoadMoreButtonVisible(offset() < (shouts()?.length || 0)))
return (
<PageLayout withPadding={true} title={`${t('Discours')} :: ${t('Feed')}`}>
<PageLayout
withPadding={true}
title={`${t('Discours')} :: ${t('Feed')}`}
key="feed"
desc="Independent media project about culture, science, art and society with horizontal editing"
>
<ReactionsProvider>
<Feed shouts={shouts() || []} />
</ReactionsProvider>

View File

@ -22,7 +22,12 @@ export default (props: RouteSectionProps<{ topics: Topic[] }>) => {
const { addTopics } = useTopics()
createEffect(() => addTopics(topics() || []))
return (
<PageLayout withPadding={true} title={`${t('Discours')} :: ${t('All topics')}`}>
<PageLayout
withPadding={true}
key="topics"
title={`${t('Discours')} :: ${t('All topics')}`}
desc="Thematic table of contents of the magazine. Here you can find all the topics that the community authors wrote about"
>
<ReactionsProvider>
<Suspense fallback={<Loading />}>
<AllTopics topics={topics() as Topic[]} />

View File

@ -9,6 +9,8 @@ import { ReactionsProvider } from '~/context/reactions'
import { useTopics } from '~/context/topics'
import { loadShouts } from '~/graphql/api/public'
import { LoadShoutsOptions, Shout, Topic } from '~/graphql/schema/core.gen'
import { getImageUrl } from '~/lib/getImageUrl'
import { getArticleDescription } from '~/utils/meta'
import { SHOUTS_PER_PAGE } from '../(home)'
const fetchTopicShouts = async (slug: string, offset?: number) => {
@ -43,15 +45,26 @@ export default (props: RouteSectionProps<{ articles: Shout[] }>) => {
})
}
})
const desc = createMemo(() =>
topic()?.body
? getArticleDescription(topic()?.body || '')
: t('The most interesting publications on the topic', { topicName: title() })
)
const cover = createMemo(() =>
topic()?.pic
? getImageUrl(topic()?.pic || '', { width: 1200 })
: getImageUrl('production/image/logo_image.png')
)
return (
<ErrorBoundary fallback={(_err) => <FourOuFourView />}>
<Suspense fallback={<Loading />}>
<PageLayout
key="topic"
title={title()}
desc={desc()}
headerTitle={topic()?.title || ''}
slug={topic()?.slug}
articleBody={topic()?.body || ''}
cover={topic()?.pic || ''}
cover={cover()}
>
<ReactionsProvider>
<TopicView