diff --git a/src/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx index 25863788..c8e7c6ff 100644 --- a/src/components/Article/FullArticle.tsx +++ b/src/components/Article/FullArticle.tsx @@ -1,4 +1,3 @@ -// import { install } from 'ga-gtag' import { createPopper } from '@popperjs/core' import { Link } from '@solidjs/meta' import { A, useSearchParams } from '@solidjs/router' @@ -10,7 +9,8 @@ import { useLocalize } from '~/context/localize' import { useReactions } from '~/context/reactions' 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 type { Author, Maybe, Shout, Topic } from '~/graphql/schema/core.gen' +import { processPrepositions } from '~/intl/prepositions' import { isCyrillic } from '~/intl/translate' import { getImageUrl } from '~/lib/getImageUrl' import { MediaItem } from '~/types/mediaitem' @@ -18,6 +18,7 @@ import { capitalize } from '~/utils/capitalize' import { AuthorBadge } from '../Author/AuthorBadge' import { CardTopic } from '../Feed/CardTopic' import { FeedArticlePopup } from '../Feed/FeedArticlePopup' +import stylesHeader from '../Nav/Header/Header.module.scss' import { Modal } from '../Nav/Modal' import { TableOfContents } from '../TableOfContents' import { Icon } from '../_shared/Icon' @@ -28,15 +29,13 @@ import { Popover } from '../_shared/Popover' import { ShareModal } from '../_shared/ShareModal' import { ImageSwiper } from '../_shared/SolidSwiper' import { VideoPlayer } from '../_shared/VideoPlayer' +import styles from './Article.module.scss' import { AudioHeader } from './AudioHeader' import { AudioPlayer } from './AudioPlayer' import { CommentsTree } from './CommentsTree' import { SharePopup, getShareUrl } from './SharePopup' import { ShoutRatingControl } from './ShoutRatingControl' -import stylesHeader from '../Nav/Header/Header.module.scss' -import styles from './Article.module.scss' - type Props = { article: Shout scrollToComments?: boolean @@ -64,6 +63,8 @@ const scrollTo = (el: HTMLElement) => { } const imgSrcRegExp = /]+src\s*=\s*["']([^"']+)["']/gi +const COMMENTS_PER_PAGE = 30 +const VOTES_PER_PAGE = 50 export const FullArticle = (props: Props) => { const [searchParams, changeSearchParams] = useSearchParams() @@ -76,9 +77,29 @@ export const FullArticle = (props: Props) => { const { session, requireAuthentication } = useSession() const author = createMemo(() => session()?.user?.app_data?.profile as Author) const { addSeen } = useFeed() - const formattedDate = createMemo(() => formatDate(new Date((props.article.published_at || 0) * 1000))) + const [pages, setPages] = createSignal>({}) + createEffect( + on( + pages, + async (p: Record) => { + await loadReactionsBy({ + by: { shout: props.article.slug, comment: true }, + limit: COMMENTS_PER_PAGE, + offset: COMMENTS_PER_PAGE * p.comments || 0 + }) + await loadReactionsBy({ + by: { shout: props.article.slug, rating: true }, + limit: VOTES_PER_PAGE, + offset: VOTES_PER_PAGE * p.rating || 0 + }) + setIsReactionsLoaded(true) + }, + { defer: true } + ) + ) + const canEdit = createMemo( () => Boolean(author()?.id) && @@ -110,14 +131,14 @@ export const FullArticle = (props: Props) => { if (props.article.media) { const media = JSON.parse(props.article.media) if (media.length > 0) { - return media[0].body + return processPrepositions(media[0].body) } } } catch (error) { console.error(error) } } - return props.article.body || '' + return processPrepositions(props.article.body) || '' }) const imageUrls = createMemo(() => { @@ -141,13 +162,7 @@ export const FullArticle = (props: Props) => { return Array.from(imageElements).map((img) => img.src) }) - const media = createMemo(() => { - try { - return JSON.parse(props.article.media || '[]') - } catch { - return [] - } - }) + const media = createMemo(() => JSON.parse(props.article.media || '[]')) let commentsRef: HTMLDivElement | undefined @@ -291,40 +306,25 @@ export const FullArticle = (props: Props) => { }) } - createEffect( - on( - () => props.article, - () => { - updateIframeSizes() - } - ) - ) - - onMount(async () => { - const opts: QueryLoad_Reactions_ByArgs = { by: { shout: props.article.slug }, limit: 999, offset: 0 } - const _rrr = await loadReactionsBy(opts) + onMount(() => { + console.debug(props.article) + setPages((_) => ({comments: 0, rating: 0})) addSeen(props.article.slug) - setIsReactionsLoaded(true) document.title = props.article.title + updateIframeSizes() window?.addEventListener('resize', updateIframeSizes) - onCleanup(() => window.removeEventListener('resize', updateIframeSizes)) - - createEffect(() => { - if (props.scrollToComments && commentsRef) { - scrollTo(commentsRef) - } - }) - - createEffect(() => { - if (searchParams?.scrollTo === 'comments' && commentsRef) { - requestAnimationFrame(() => commentsRef && scrollTo(commentsRef)) - changeSearchParams({ scrollTo: undefined }) - } - }) }) - const shareUrl = getShareUrl({ pathname: `/${props.article.slug || ''}` }) + createEffect(() => props.scrollToComments && commentsRef && scrollTo(commentsRef)) + createEffect(() => { + if (searchParams?.scrollTo === 'comments' && commentsRef) { + requestAnimationFrame(() => commentsRef && scrollTo(commentsRef)) + changeSearchParams({ scrollTo: undefined }) + } + }) + + const shareUrl = createMemo(() => getShareUrl({ pathname: `/${props.article.slug || ''}` })) const getAuthorName = (a: Author) => lang() === 'en' && isCyrillic(a.name || '') ? capitalize(a.slug.replace(/-/, ' ')) : a.name return ( @@ -346,7 +346,7 @@ export const FullArticle = (props: Props) => {

{props.article.title || ''}

-

{props.article.subtitle || ''}

+

{processPrepositions(props.article.subtitle || '')}

@@ -378,7 +378,7 @@ export const FullArticle = (props: Props) => {
-
+
{ title={props.article.title} description={props.article.description || body() || media()[0]?.body} imageUrl={props.article.cover || ''} - shareUrl={shareUrl} + shareUrl={shareUrl()} containerCssClass={stylesHeader.control} onVisibilityChange={(isVisible) => setIsActionPopupActive(isVisible)} trigger={ @@ -600,7 +600,7 @@ export const FullArticle = (props: Props) => { title={props.article.title} description={props.article.description || body() || media()[0]?.body} imageUrl={props.article.cover || ''} - shareUrl={shareUrl} + shareUrl={shareUrl()} /> ) diff --git a/src/context/following.tsx b/src/context/following.tsx index 13a4810c..15512ef1 100644 --- a/src/context/following.tsx +++ b/src/context/following.tsx @@ -33,7 +33,21 @@ interface FollowingContextType { unfollow: (what: FollowingEntity, slug: string) => Promise } -const FollowingContext = createContext({} as FollowingContextType) +const defaultFollowing = { + slug: '', + type: 'follow' +} as FollowingData + +const FollowingContext = createContext({ + following: () => defaultFollowing, + followers: () => [], + loading: () => false, + setFollows: (_follows: AuthorFollowsResult) => undefined, + follows: {}, + loadFollows: () => undefined, + follow: (_what: FollowingEntity, _slug: string) => undefined, + unfollow: (_what: FollowingEntity, _slug: string) => undefined +} as unknown as FollowingContextType) export function useFollowing() { return useContext(FollowingContext) @@ -51,8 +65,6 @@ const EMPTY_SUBSCRIPTIONS: AuthorFollowsResult = { communities: [] as Community[] } -const defaultFollowing = { slug: '', type: 'follow' } as FollowingData - export const FollowingProvider = (props: { children: JSX.Element }) => { const [loading, setLoading] = createSignal(false) const [followers, setFollowers] = createSignal([] as Author[]) diff --git a/src/context/reactions.tsx b/src/context/reactions.tsx index c9aadab0..7e734ebe 100644 --- a/src/context/reactions.tsx +++ b/src/context/reactions.tsx @@ -40,6 +40,7 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => { const { mutation } = useGraphQL() const loadReactionsBy = async (opts: QueryLoad_Reactions_ByArgs): Promise => { + !opts.by && console.warn('reactions provider got wrong opts') const fetcher = await loadReactions(opts) const result = (await fetcher()) || [] console.debug('[context.reactions] loaded', result) diff --git a/src/graphql/api/public.ts b/src/graphql/api/public.ts index de726531..5ef2049b 100644 --- a/src/graphql/api/public.ts +++ b/src/graphql/api/public.ts @@ -1,5 +1,6 @@ import { cache } from '@solidjs/router' import { defaultClient } from '~/context/graphql' +import getShoutQuery from '~/graphql/query/core/article-load' import loadShoutsByQuery from '~/graphql/query/core/articles-load-by' import loadShoutsSearchQuery from '~/graphql/query/core/articles-load-search' import getAuthorQuery from '~/graphql/query/core/author-by' @@ -69,10 +70,10 @@ export const loadReactions = (options: QueryLoad_Reactions_ByArgs) => { } export const getShout = (options: QueryGet_ShoutArgs) => { - console.debug('[lib.api] get shout options', options) + // console.debug('[lib.api] get shout options', options) return cache( async () => { - const resp = await defaultClient.query(loadReactionsByQuery, { ...options }).toPromise() + const resp = await defaultClient.query(getShoutQuery, { ...options }).toPromise() const result = resp?.data?.get_shout if (result) return result as Shout }, diff --git a/src/routes/[slug].tsx b/src/routes/[slug].tsx index 7a294039..b0e9746c 100644 --- a/src/routes/[slug].tsx +++ b/src/routes/[slug].tsx @@ -77,7 +77,7 @@ export default ( }> }> diff --git a/src/routes/topic/[slug].tsx b/src/routes/topic/[slug].tsx index 35ffb375..00b2d1bb 100644 --- a/src/routes/topic/[slug].tsx +++ b/src/routes/topic/[slug].tsx @@ -28,9 +28,7 @@ export const route = { export default (props: RouteSectionProps<{ articles: Shout[] }>) => { const params = useParams() - const articles = createAsync( - async () => props.data.articles || (await fetchTopicShouts(params.slug)) || [] - ) + const articles = createAsync(async () => props.data.articles || (await fetchTopicShouts(params.slug)) || []) const { topicEntities } = useTopics() const { t } = useLocalize() const topic = createMemo(() => topicEntities?.()[params.slug])