meta-refactored
This commit is contained in:
parent
b204204a31
commit
e3ac3cc406
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export { AuthorsList } from './AuthorsList'
|
|
@ -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={
|
||||
<>
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export { Topics } from './Topics'
|
|
@ -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 (
|
1
src/components/Nav/TopicsNav/index.ts
Normal file
1
src/components/Nav/TopicsNav/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { TopicsNav } from './TopicsNav'
|
|
@ -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}>
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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 />}>
|
||||
<>
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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')}>
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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}`)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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, Дискурс, статьи, журналистика, исследование"
|
||||
|
|
|
@ -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)} />
|
||||
|
|
|
@ -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)}
|
||||
>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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[]} />
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user