isolate-utils-authors-with-@

This commit is contained in:
Untone 2024-07-13 12:06:49 +03:00
parent 645e65751b
commit ef1408327f
13 changed files with 160 additions and 128 deletions

View File

@ -4,7 +4,7 @@ import { Icon } from '~/components/_shared/Icon'
import { Popover } from '~/components/_shared/Popover'
import { useLocalize } from '~/context/localize'
import { MediaItem } from '~/types/mediaitem'
import { getArticleDescription } from '~/utils/meta'
import { descFromBody } from '~/utils/meta'
import { SharePopup, getShareUrl } from '../SharePopup'
import styles from './AudioPlayer.module.scss'
@ -137,7 +137,7 @@ export const PlayerPlaylist = (props: Props) => {
>
<SharePopup
title={mi.title}
description={getArticleDescription(props.body || '')}
description={descFromBody(props.body || '')}
imageUrl={mi.pic || ''}
shareUrl={getShareUrl({ pathname: `/${props.articleSlug}` })}
trigger={

View File

@ -8,7 +8,7 @@ import { useLocalize } from '~/context/localize'
import { useSession } from '~/context/session'
import type { Author, Maybe, Shout, Topic } from '~/graphql/schema/core.gen'
import { capitalize } from '~/utils/capitalize'
import { getArticleDescription } from '~/utils/meta'
import { descFromBody } from '~/utils/meta'
import { CoverImage } from '../../Article/CoverImage'
import { SharePopup, getShareUrl } from '../../Article/SharePopup'
import { ShoutRatingControl } from '../../Article/ShoutRatingControl'
@ -109,7 +109,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
const [isActionPopupActive, setIsActionPopupActive] = createSignal(false)
const [isCoverImageLoadError, setIsCoverImageLoadError] = createSignal(false)
const [isCoverImageLoading, setIsCoverImageLoading] = createSignal(true)
const description = getArticleDescription(props.article?.body)
const description = descFromBody(props.article?.body)
const aspectRatio: Accessor<string> = () => LAYOUT_ASPECT[props.article?.layout as string]
const [mainTopicTitle, mainTopicSlug] = getMainTopicTitle(props.article, lang())
const { title, subtitle } = getTitleAndSubtitle(props.article)

View File

@ -4,7 +4,7 @@ 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 { SHOUTS_PER_PAGE } from '~/routes/(home)'
import { SHOUTS_PER_PAGE } from '~/routes/(main)'
import { capitalize } from '~/utils/capitalize'
import { splitToPages } from '~/utils/splitToPages'
import Banner from '../Discours/Banner'

View File

@ -7,7 +7,7 @@ import { useLocalize } from '~/context/localize'
import { useTopics } from '~/context/topics'
import { loadAuthors, loadFollowersByTopic, loadShouts } from '~/graphql/api/public'
import { Author, AuthorsBy, LoadShoutsOptions, Shout, Topic } from '~/graphql/schema/core.gen'
import { SHOUTS_PER_PAGE } from '~/routes/(home)'
import { SHOUTS_PER_PAGE } from '~/routes/(main)'
import { getUnixtime } from '~/utils/getServerDate'
import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll'
import { splitToPages } from '~/utils/splitToPages'

View File

@ -8,7 +8,7 @@ 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 { descFromBody } from '~/utils/meta'
import { FooterView } from '../Discours/Footer'
import { Header } from '../Nav/Header'
import styles from './PageLayout.module.scss'
@ -50,7 +50,7 @@ export const PageLayout = (props: PageLayoutProps) => {
const keypath = createMemo(() => (props.key || loc?.pathname.split('/')[0]) as keyof typeof ruKeywords)
const keywords = createMemo(
() =>
(props.article && getArticleKeywords(props.article as Shout)) ||
(props.article && descFromBody(props.article.body)) ||
(lang() === 'ru' ? ruKeywords[keypath()] : enKeywords[keypath()])
)
const [scrollToComments, setScrollToComments] = createSignal<boolean>(false)

View File

@ -1,104 +0,0 @@
import { RouteDefinition, RouteSectionProps, createAsync, useLocation, useParams } from '@solidjs/router'
import { HttpStatusCode } from '@solidjs/start'
import {
ErrorBoundary,
Show,
Suspense,
createEffect,
createMemo,
createSignal,
on,
onMount
} from 'solid-js'
import { FourOuFourView } from '~/components/Views/FourOuFour'
import { Loading } from '~/components/_shared/Loading'
import { gaIdentity } from '~/config'
import { useLocalize } from '~/context/localize'
import { getShout } from '~/graphql/api/public'
import type { Reaction, 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'
const fetchShout = async (slug: string): Promise<Shout | undefined> => {
const shoutLoader = getShout({ slug })
const result = await shoutLoader()
return result
}
export const route: RouteDefinition = {
load: async ({ params }) => ({
article: await fetchShout(params.slug)
})
}
export default (
props: RouteSectionProps<{ article?: Shout; comments?: Reaction[]; votes?: Reaction[] }>
) => {
const params = useParams()
const loc = useLocation()
const { t } = useLocalize()
const [scrollToComments, setScrollToComments] = createSignal<boolean>(false)
const article = createAsync(async () => props.data.article || (await fetchShout(params.slug)))
const title = createMemo(
() => `${article()?.authors?.[0]?.name || t('Discours')} :: ${article()?.title || ''}`
)
onMount(async () => {
if (gaIdentity && article()?.id) {
try {
await loadGAScript(gaIdentity)
initGA(gaIdentity)
} catch (error) {
console.warn('Failed to connect Google Analytics:', error)
}
}
})
createEffect(
on(
article,
(a?: Shout) => {
if (!a?.id) return
window?.gtag?.('event', 'page_view', {
page_title: a.title,
page_location: window?.location.href || '',
page_path: loc.pathname
})
},
{ defer: true }
)
)
return (
<ErrorBoundary fallback={() => <HttpStatusCode code={500} />}>
<Suspense fallback={<Loading />}>
<Show
when={article()?.id}
fallback={
<PageLayout isHeaderFixed={false} hideFooter={true} title={t('Nothing is here')}>
<FourOuFourView />
<HttpStatusCode code={404} />
</PageLayout>
}
>
<PageLayout
title={title()}
desc={getArticleKeywords(article() as Shout)}
headerTitle={article()?.title || ''}
slug={article()?.slug}
cover={article()?.cover || ''}
scrollToComments={(value) => setScrollToComments(value)}
>
<ReactionsProvider>
<FullArticle article={article() as Shout} scrollToComments={scrollToComments()} />
</ReactionsProvider>
</PageLayout>
</Show>
</Suspense>
</ErrorBoundary>
)
}

View File

@ -0,0 +1,134 @@
import { RouteDefinition, RouteSectionProps, createAsync, useLocation, useParams } from '@solidjs/router'
import { HttpStatusCode } from '@solidjs/start'
import {
ErrorBoundary,
Show,
Suspense,
createEffect,
createMemo,
createSignal,
on,
onMount
} from 'solid-js'
import { FourOuFourView } from '~/components/Views/FourOuFour'
import { Loading } from '~/components/_shared/Loading'
import { gaIdentity } from '~/config'
import { useLocalize } from '~/context/localize'
import { getAuthor, getShout } from '~/graphql/api/public'
import type { Author, Reaction, Shout } from '~/graphql/schema/core.gen'
import { initGA, loadGAScript } from '~/utils/ga'
import { descFromBody, keywordsFromTopics } from '~/utils/meta'
import { FullArticle } from '../../components/Article/FullArticle'
import { PageLayout } from '../../components/_shared/PageLayout'
import { ReactionsProvider } from '../../context/reactions'
import AuthorPage, { AuthorPageProps } from '../author/[slug]/[...tab]'
const fetchShout = async (slug: string): Promise<Shout | undefined> => {
const shoutLoader = getShout({ slug })
const result = await shoutLoader()
return result
}
const fetchAuthor = async (slug: string): Promise<Author | undefined> => {
const authorLoader = getAuthor({ slug })
const result = await authorLoader()
return result
}
export const route: RouteDefinition = {
load: async ({ params }) => ({
article: await fetchShout(params.slug)
})
}
type SlugPageProps = { article?: Shout; comments?: Reaction[]; votes?: Reaction[]; author?: Author }
export default (props: RouteSectionProps<SlugPageProps>) => {
const params = useParams()
if (params.slug.startsWith('@')) return AuthorPage(props as RouteSectionProps<AuthorPageProps>)
const loc = useLocation()
const { t } = useLocalize()
const [scrollToComments, setScrollToComments] = createSignal<boolean>(false)
const article = createAsync(async () => props.data.article || (await fetchShout(params.slug)))
const author = createAsync(async () =>
params.slug.startsWith('@')
? props.data.author || (await fetchAuthor(params.slug))
: article()?.authors?.[0]
)
const titleSuffix = createMemo(
() => (article()?.title || author()?.name) ?? ` :: ${article()?.title || author()?.name || ''}`
)
onMount(async () => {
if (gaIdentity && article()?.id) {
try {
await loadGAScript(gaIdentity)
initGA(gaIdentity)
} catch (error) {
console.warn('Failed to connect Google Analytics:', error)
}
}
})
createEffect(
on(
article,
(a?: Shout) => {
if (!a?.id) return
window?.gtag?.('event', 'page_view', {
page_title: a.title,
page_location: window?.location.href || '',
page_path: loc.pathname
})
},
{ defer: true }
)
)
return (
<ErrorBoundary fallback={() => <HttpStatusCode code={500} />}>
<Suspense fallback={<Loading />}>
<Show
when={article()?.id}
fallback={
<PageLayout isHeaderFixed={false} hideFooter={true} title={t('Nothing is here')}>
<FourOuFourView />
<HttpStatusCode code={404} />
</PageLayout>
}
>
<Show
when={params.slug.startsWith('@')}
fallback={
<PageLayout
title={`${t('Discours')}${titleSuffix() || ''}`}
desc={keywordsFromTopics(article()?.topics as { title: string }[])}
headerTitle={article()?.title || ''}
slug={article()?.slug}
cover={article()?.cover || ''}
scrollToComments={(value) => setScrollToComments(value)}
>
<ReactionsProvider>
<FullArticle article={article() as Shout} scrollToComments={scrollToComments()} />
</ReactionsProvider>
</PageLayout>
}
>
<PageLayout
title={`${t('Discours')}${titleSuffix() || ''}`}
desc={descFromBody(author()?.about || author()?.bio || '')}
headerTitle={author()?.name || ''}
slug={author()?.slug}
cover={author()?.pic || ''}
>
<ReactionsProvider>
<FullArticle article={article() as Shout} scrollToComments={scrollToComments()} />
</ReactionsProvider>
</PageLayout>
</Show>
</Show>
</Suspense>
</ErrorBoundary>
)
}

View File

@ -16,7 +16,7 @@ import {
Topic
} from '~/graphql/schema/core.gen'
import { getImageUrl } from '~/lib/getImageUrl'
import { SHOUTS_PER_PAGE } from '../../(home)'
import { SHOUTS_PER_PAGE } from '../../(main)'
const fetchAuthorShouts = async (slug: string, offset?: number) => {
const opts: LoadShoutsOptions = { filters: { author: slug }, limit: SHOUTS_PER_PAGE, offset }
@ -47,7 +47,9 @@ export const route = {
}
}
export default (props: RouteSectionProps<{ articles: Shout[]; author: Author; topics: Topic[] }>) => {
export type AuthorPageProps = { articles?: Shout[]; author?: Author; topics?: Topic[] }
export const AuthorPage = (props: RouteSectionProps<AuthorPageProps>) => {
const params = useParams()
const { addAuthor } = useAuthors()
const articles = createAsync(
@ -55,7 +57,7 @@ export default (props: RouteSectionProps<{ articles: Shout[]; author: Author; to
)
const author = createAsync(async () => {
const a = props.data.author || (await fetchAuthor(params.slug))
addAuthor(a)
a && addAuthor(a)
return a
})
const topics = createAsync(async () => props.data.topics || (await fetchAllTopics()))
@ -104,3 +106,5 @@ export default (props: RouteSectionProps<{ articles: Shout[]; author: Author; to
</ErrorBoundary>
)
}
export default AuthorPage

View File

@ -7,7 +7,7 @@ import { useLocalize } from '~/context/localize'
import { loadShouts } from '~/graphql/api/public'
import { LoadShoutsOptions, Shout } from '~/graphql/schema/core.gen'
import { LayoutType } from '~/types/common'
import { SHOUTS_PER_PAGE } from '../(home)'
import { SHOUTS_PER_PAGE } from '../(main)'
const fetchExpoShouts = async (layouts: string[]) => {
const result = await loadShouts({

View File

@ -7,7 +7,7 @@ import { useLocalize } from '~/context/localize'
import { ReactionsProvider } from '~/context/reactions'
import { loadShouts } from '~/graphql/api/public'
import { LoadShoutsOptions, Shout } from '~/graphql/schema/core.gen'
import { SHOUTS_PER_PAGE } from '../(home)'
import { SHOUTS_PER_PAGE } from '../(main)'
export type FeedPeriod = 'week' | 'month' | 'year'

View File

@ -10,8 +10,8 @@ import { useTopics } from '~/context/topics'
import { loadShouts, loadTopics } 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)'
import { descFromBody } from '~/utils/meta'
import { SHOUTS_PER_PAGE } from '../(main)'
const fetchTopicShouts = async (slug: string, offset?: number) => {
const opts: LoadShoutsOptions = { filters: { topic: slug }, limit: SHOUTS_PER_PAGE, offset }
@ -72,7 +72,7 @@ export default (props: RouteSectionProps<{ articles: Shout[]; topics: Topic[] }>
const desc = createMemo(() =>
topic()?.body
? getArticleDescription(topic()?.body || '')
? descFromBody(topic()?.body || '')
: t('The most interesting publications on the topic', { topicName: title() })
)

View File

@ -1,16 +1,14 @@
import { Shout } from '~/graphql/schema/core.gen'
const MAX_DESCRIPTION_LENGTH = 150
export const getArticleDescription = (body: string): string => {
export const descFromBody = (body: string): string => {
if (!body) {
return ''
}
const descriptionWordsArray = body
.replaceAll(/<[^>]*>/g, ' ')
.replaceAll(/\s+/g, ' ')
.replace(/<[^>]*>/g, ' ') // Remove HTML tags
.replace(/\s+/g, ' ') // Normalize whitespace
.split(' ')
// ¯\_(ツ)_/¯ maybe need to remove the punctuation
let description = ''
let i = 0
while (i < descriptionWordsArray.length && description.length < MAX_DESCRIPTION_LENGTH) {
@ -20,6 +18,6 @@ export const getArticleDescription = (body: string): string => {
return description.trim()
}
export const getArticleKeywords = (shout: Shout): string => {
return (shout.topics || [])?.map((topic) => topic?.title).join(', ')
export const keywordsFromTopics = (topics: { title: string }[]): string => {
return topics.map((topic: { title: string }) => topic.title).join(', ')
}