scrollto+shoutreaction
This commit is contained in:
parent
1ec368eae7
commit
260b95f692
|
@ -47,7 +47,7 @@ export const Comment = (props: Props) => {
|
||||||
const [editedBody, setEditedBody] = createSignal<string>()
|
const [editedBody, setEditedBody] = createSignal<string>()
|
||||||
const { session } = useSession()
|
const { session } = useSession()
|
||||||
const author = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
|
const author = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
|
||||||
const { createReaction, updateReaction } = useReactions()
|
const { createShoutReaction, updateShoutReaction } = useReactions()
|
||||||
const { showConfirm } = useUI()
|
const { showConfirm } = useUI()
|
||||||
const { showSnackbar } = useSnackbar()
|
const { showSnackbar } = useSnackbar()
|
||||||
const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token))
|
const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token))
|
||||||
|
@ -99,7 +99,7 @@ export const Comment = (props: Props) => {
|
||||||
const handleCreate = async (value: string) => {
|
const handleCreate = async (value: string) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
await createReaction({
|
await createShoutReaction({
|
||||||
reaction: {
|
reaction: {
|
||||||
kind: ReactionKind.Comment,
|
kind: ReactionKind.Comment,
|
||||||
reply_to: props.comment.id,
|
reply_to: props.comment.id,
|
||||||
|
@ -123,7 +123,7 @@ export const Comment = (props: Props) => {
|
||||||
const handleUpdate = async (value: string) => {
|
const handleUpdate = async (value: string) => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
const reaction = await updateReaction({
|
const reaction = await updateShoutReaction({
|
||||||
reaction: {
|
reaction: {
|
||||||
id: props.comment.id || 0,
|
id: props.comment.id || 0,
|
||||||
kind: ReactionKind.Comment,
|
kind: ReactionKind.Comment,
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const CommentRatingControl = (props: Props) => {
|
||||||
const { session } = useSession()
|
const { session } = useSession()
|
||||||
const uid = createMemo<number>(() => session()?.user?.app_data?.profile?.id || 0)
|
const uid = createMemo<number>(() => session()?.user?.app_data?.profile?.id || 0)
|
||||||
const { showSnackbar } = useSnackbar()
|
const { showSnackbar } = useSnackbar()
|
||||||
const { reactionEntities, createReaction, deleteReaction, loadReactionsBy } = useReactions()
|
const { reactionEntities, createShoutReaction, deleteShoutReaction, loadReactionsBy } = useReactions()
|
||||||
|
|
||||||
const checkReaction = (reactionKind: ReactionKind) =>
|
const checkReaction = (reactionKind: ReactionKind) =>
|
||||||
Object.values(reactionEntities).some(
|
Object.values(reactionEntities).some(
|
||||||
|
@ -53,7 +53,7 @@ export const CommentRatingControl = (props: Props) => {
|
||||||
r.shout.id === props.comment.shout.id &&
|
r.shout.id === props.comment.shout.id &&
|
||||||
r.reply_to === props.comment.id
|
r.reply_to === props.comment.id
|
||||||
)
|
)
|
||||||
if (reactionToDelete) return deleteReaction(reactionToDelete.id)
|
if (reactionToDelete) return deleteShoutReaction(reactionToDelete.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRatingChange = async (isUpvote: boolean) => {
|
const handleRatingChange = async (isUpvote: boolean) => {
|
||||||
|
@ -63,7 +63,7 @@ export const CommentRatingControl = (props: Props) => {
|
||||||
} else if (isDownvoted()) {
|
} else if (isDownvoted()) {
|
||||||
await deleteCommentReaction(ReactionKind.Dislike)
|
await deleteCommentReaction(ReactionKind.Dislike)
|
||||||
} else {
|
} else {
|
||||||
await createReaction({
|
await createShoutReaction({
|
||||||
reaction: {
|
reaction: {
|
||||||
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
|
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
|
||||||
shout: props.comment.shout.id,
|
shout: props.comment.shout.id,
|
||||||
|
|
|
@ -29,7 +29,7 @@ export const CommentsTree = (props: Props) => {
|
||||||
const [newReactions, setNewReactions] = createSignal<Reaction[]>([])
|
const [newReactions, setNewReactions] = createSignal<Reaction[]>([])
|
||||||
const [clearEditor, setClearEditor] = createSignal(false)
|
const [clearEditor, setClearEditor] = createSignal(false)
|
||||||
const [clickedReplyId, setClickedReplyId] = createSignal<number>()
|
const [clickedReplyId, setClickedReplyId] = createSignal<number>()
|
||||||
const { reactionEntities, createReaction, loadReactionsBy } = useReactions()
|
const { reactionEntities, createShoutReaction, loadReactionsBy } = useReactions()
|
||||||
|
|
||||||
const comments = createMemo(() =>
|
const comments = createMemo(() =>
|
||||||
Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT')
|
Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT')
|
||||||
|
@ -74,7 +74,7 @@ export const CommentsTree = (props: Props) => {
|
||||||
const handleSubmitComment = async (value: string) => {
|
const handleSubmitComment = async (value: string) => {
|
||||||
setPosting(true)
|
setPosting(true)
|
||||||
try {
|
try {
|
||||||
await createReaction({
|
await createShoutReaction({
|
||||||
reaction: {
|
reaction: {
|
||||||
kind: ReactionKind.Comment,
|
kind: ReactionKind.Comment,
|
||||||
body: value,
|
body: value,
|
||||||
|
|
|
@ -38,7 +38,6 @@ import { ShoutRatingControl } from './ShoutRatingControl'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
article: Shout
|
article: Shout
|
||||||
scrollToComments?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type IframeSize = {
|
type IframeSize = {
|
||||||
|
@ -47,8 +46,7 @@ type IframeSize = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ArticlePageSearchParams = {
|
export type ArticlePageSearchParams = {
|
||||||
scrollTo: 'comments'
|
commentId?: string
|
||||||
commentId: string
|
|
||||||
slide?: string
|
slide?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +65,7 @@ export const COMMENTS_PER_PAGE = 30
|
||||||
const VOTES_PER_PAGE = 50
|
const VOTES_PER_PAGE = 50
|
||||||
|
|
||||||
export const FullArticle = (props: Props) => {
|
export const FullArticle = (props: Props) => {
|
||||||
const [searchParams, changeSearchParams] = useSearchParams<ArticlePageSearchParams>()
|
const [searchParams] = useSearchParams<ArticlePageSearchParams>()
|
||||||
const { showModal } = useUI()
|
const { showModal } = useUI()
|
||||||
const { loadReactionsBy } = useReactions()
|
const { loadReactionsBy } = useReactions()
|
||||||
const [selectedImage, setSelectedImage] = createSignal('')
|
const [selectedImage, setSelectedImage] = createSignal('')
|
||||||
|
@ -83,18 +81,20 @@ export const FullArticle = (props: Props) => {
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
pages,
|
pages,
|
||||||
async (p: Record<string, number>) => {
|
(p: Record<string, number>) => {
|
||||||
await loadReactionsBy({
|
console.debug('content paginated')
|
||||||
|
loadReactionsBy({
|
||||||
by: { shout: props.article.slug, comment: true },
|
by: { shout: props.article.slug, comment: true },
|
||||||
limit: COMMENTS_PER_PAGE,
|
limit: COMMENTS_PER_PAGE,
|
||||||
offset: COMMENTS_PER_PAGE * p.comments || 0
|
offset: COMMENTS_PER_PAGE * p.comments || 0
|
||||||
})
|
})
|
||||||
await loadReactionsBy({
|
loadReactionsBy({
|
||||||
by: { shout: props.article.slug, rating: true },
|
by: { shout: props.article.slug, rating: true },
|
||||||
limit: VOTES_PER_PAGE,
|
limit: VOTES_PER_PAGE,
|
||||||
offset: VOTES_PER_PAGE * p.rating || 0
|
offset: VOTES_PER_PAGE * p.rating || 0
|
||||||
})
|
})
|
||||||
setIsReactionsLoaded(true)
|
setIsReactionsLoaded(true)
|
||||||
|
console.debug('reactions paginated')
|
||||||
},
|
},
|
||||||
{ defer: true }
|
{ defer: true }
|
||||||
)
|
)
|
||||||
|
@ -165,15 +165,16 @@ export const FullArticle = (props: Props) => {
|
||||||
const media = createMemo<MediaItem[]>(() => JSON.parse(props.article.media || '[]'))
|
const media = createMemo<MediaItem[]>(() => JSON.parse(props.article.media || '[]'))
|
||||||
|
|
||||||
let commentsRef: HTMLDivElement | undefined
|
let commentsRef: HTMLDivElement | undefined
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (searchParams?.commentId && isReactionsLoaded()) {
|
if (searchParams?.commentId && isReactionsLoaded()) {
|
||||||
const commentElement = document.querySelector<HTMLElement>(
|
console.debug('comment id is in link, scroll to')
|
||||||
`[id='comment_${searchParams?.commentId}']`
|
const scrollToElement =
|
||||||
)
|
document.querySelector<HTMLElement>(`[id='comment_${searchParams?.commentId}']`) ||
|
||||||
|
commentsRef ||
|
||||||
|
document.body
|
||||||
|
|
||||||
if (commentElement) {
|
if (scrollToElement) {
|
||||||
requestAnimationFrame(() => scrollTo(commentElement))
|
requestAnimationFrame(() => scrollTo(scrollToElement))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -316,14 +317,6 @@ export const FullArticle = (props: Props) => {
|
||||||
onCleanup(() => window.removeEventListener('resize', updateIframeSizes))
|
onCleanup(() => window.removeEventListener('resize', updateIframeSizes))
|
||||||
})
|
})
|
||||||
|
|
||||||
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 shareUrl = createMemo(() => getShareUrl({ pathname: `/${props.article.slug || ''}` }))
|
||||||
const getAuthorName = (a: Author) =>
|
const getAuthorName = (a: Author) =>
|
||||||
lang() === 'en' && isCyrillic(a.name || '') ? capitalize(a.slug.replace(/-/, ' ')) : a.name
|
lang() === 'en' && isCyrillic(a.name || '') ? capitalize(a.slug.replace(/-/, ' ')) : a.name
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
||||||
const { loadShout } = useFeed()
|
const { loadShout } = useFeed()
|
||||||
const { requireAuthentication, session } = useSession()
|
const { requireAuthentication, session } = useSession()
|
||||||
const author = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
|
const author = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
|
||||||
const { reactionEntities, createReaction, deleteReaction, loadReactionsBy } = useReactions()
|
const { reactionEntities, createShoutReaction, deleteShoutReaction, loadReactionsBy } = useReactions()
|
||||||
const [isLoading, setIsLoading] = createSignal(false)
|
const [isLoading, setIsLoading] = createSignal(false)
|
||||||
|
|
||||||
const checkReaction = (reactionKind: ReactionKind) =>
|
const checkReaction = (reactionKind: ReactionKind) =>
|
||||||
|
@ -43,7 +43,7 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const deleteShoutReaction = async (reactionKind: ReactionKind) => {
|
const removeReaction = async (reactionKind: ReactionKind) => {
|
||||||
const reactionToDelete = Object.values(reactionEntities).find(
|
const reactionToDelete = Object.values(reactionEntities).find(
|
||||||
(r) =>
|
(r) =>
|
||||||
r.kind === reactionKind &&
|
r.kind === reactionKind &&
|
||||||
|
@ -51,18 +51,18 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
||||||
r.shout.id === props.shout.id &&
|
r.shout.id === props.shout.id &&
|
||||||
!r.reply_to
|
!r.reply_to
|
||||||
)
|
)
|
||||||
if (reactionToDelete) return deleteReaction(reactionToDelete.id)
|
if (reactionToDelete) return deleteShoutReaction(reactionToDelete.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRatingChange = (isUpvote: boolean) => {
|
const handleRatingChange = (isUpvote: boolean) => {
|
||||||
requireAuthentication(async () => {
|
requireAuthentication(async () => {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
if (isUpvoted()) {
|
if (isUpvoted()) {
|
||||||
await deleteShoutReaction(ReactionKind.Like)
|
await removeReaction(ReactionKind.Like)
|
||||||
} else if (isDownvoted()) {
|
} else if (isDownvoted()) {
|
||||||
await deleteShoutReaction(ReactionKind.Dislike)
|
await removeReaction(ReactionKind.Dislike)
|
||||||
} else {
|
} else {
|
||||||
await createReaction({
|
await createShoutReaction({
|
||||||
reaction: {
|
reaction: {
|
||||||
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
|
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
|
||||||
shout: props.shout.id
|
shout: props.shout.id
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { A, useNavigate, useSearchParams } from '@solidjs/router'
|
import { A, useNavigate } from '@solidjs/router'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { Accessor, For, Show, createMemo, createSignal } from 'solid-js'
|
import { Accessor, For, Show, createMemo, createSignal } from 'solid-js'
|
||||||
import { Icon } from '~/components/_shared/Icon'
|
import { Icon } from '~/components/_shared/Icon'
|
||||||
|
@ -105,7 +105,6 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
const { t, lang, formatDate } = useLocalize()
|
const { t, lang, formatDate } = useLocalize()
|
||||||
const { session } = useSession()
|
const { session } = useSession()
|
||||||
const author = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
|
const author = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
|
||||||
const [, changeSearchParams] = useSearchParams()
|
|
||||||
const [isActionPopupActive, setIsActionPopupActive] = createSignal(false)
|
const [isActionPopupActive, setIsActionPopupActive] = createSignal(false)
|
||||||
const [isCoverImageLoadError, setIsCoverImageLoadError] = createSignal(false)
|
const [isCoverImageLoadError, setIsCoverImageLoadError] = createSignal(false)
|
||||||
const [isCoverImageLoading, setIsCoverImageLoading] = createSignal(true)
|
const [isCoverImageLoading, setIsCoverImageLoading] = createSignal(true)
|
||||||
|
@ -129,10 +128,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
|
|
||||||
const scrollToComments = (event: MouseEvent & { currentTarget: HTMLAnchorElement; target: Element }) => {
|
const scrollToComments = (event: MouseEvent & { currentTarget: HTMLAnchorElement; target: Element }) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
changeSearchParams({
|
navigate(`/${props.article.slug}?commentId=0`)
|
||||||
scrollTo: 'comments'
|
|
||||||
})
|
|
||||||
navigate(`/${props.article.slug}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onInvite = () => {
|
const onInvite = () => {
|
||||||
|
|
|
@ -23,7 +23,6 @@ type Props = {
|
||||||
isHeaderFixed?: boolean
|
isHeaderFixed?: boolean
|
||||||
desc?: string
|
desc?: string
|
||||||
cover?: string
|
cover?: string
|
||||||
scrollToComments?: (value: boolean) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeaderSearchParams = {
|
type HeaderSearchParams = {
|
||||||
|
@ -38,7 +37,7 @@ export const Header = (props: Props) => {
|
||||||
const { t, lang } = useLocalize()
|
const { t, lang } = useLocalize()
|
||||||
const { modal } = useUI()
|
const { modal } = useUI()
|
||||||
const { requireAuthentication } = useSession()
|
const { requireAuthentication } = useSession()
|
||||||
const [searchParams] = useSearchParams<HeaderSearchParams>()
|
const [searchParams, changeSearchParams] = useSearchParams<HeaderSearchParams>()
|
||||||
const [getIsScrollingBottom, setIsScrollingBottom] = createSignal(false)
|
const [getIsScrollingBottom, setIsScrollingBottom] = createSignal(false)
|
||||||
const [getIsScrolled, setIsScrolled] = createSignal(false)
|
const [getIsScrolled, setIsScrolled] = createSignal(false)
|
||||||
const [fixed, setFixed] = createSignal(false)
|
const [fixed, setFixed] = createSignal(false)
|
||||||
|
@ -85,14 +84,6 @@ export const Header = (props: Props) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const scrollToComments = (
|
|
||||||
event: MouseEvent & { currentTarget: HTMLDivElement; target: Element },
|
|
||||||
value: boolean
|
|
||||||
) => {
|
|
||||||
event.preventDefault()
|
|
||||||
props.scrollToComments?.(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBookmarkButtonClick = (ev: { preventDefault: () => void }) => {
|
const handleBookmarkButtonClick = (ev: { preventDefault: () => void }) => {
|
||||||
requireAuthentication(() => {
|
requireAuthentication(() => {
|
||||||
// TODO: implement bookmark clicked
|
// TODO: implement bookmark clicked
|
||||||
|
@ -320,7 +311,7 @@ export const Header = (props: Props) => {
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<div onClick={(event) => scrollToComments(event, true)} class={styles.control}>
|
<div onClick={() => changeSearchParams({ commentId: 0 })} class={styles.control}>
|
||||||
<Icon name="comment" class={styles.icon} />
|
<Icon name="comment" class={styles.icon} />
|
||||||
<Icon name="comment-hover" class={clsx(styles.icon, styles.iconHover)} />
|
<Icon name="comment-hover" class={clsx(styles.icon, styles.iconHover)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -175,7 +175,7 @@ export const AuthorView = (props: AuthorViewProps) => {
|
||||||
const [loadMoreCommentsHidden, setLoadMoreCommentsHidden] = createSignal(
|
const [loadMoreCommentsHidden, setLoadMoreCommentsHidden] = createSignal(
|
||||||
Boolean(props.author?.stat && props.author?.stat?.comments === 0)
|
Boolean(props.author?.stat && props.author?.stat?.comments === 0)
|
||||||
)
|
)
|
||||||
const { commentsByAuthor, addReactions } = useReactions()
|
const { commentsByAuthor, addShoutReactions } = useReactions()
|
||||||
const loadMoreComments = async () => {
|
const loadMoreComments = async () => {
|
||||||
if (!author()) return [] as LoadMoreItems
|
if (!author()) return [] as LoadMoreItems
|
||||||
saveScrollPosition()
|
saveScrollPosition()
|
||||||
|
@ -189,7 +189,7 @@ export const AuthorView = (props: AuthorViewProps) => {
|
||||||
offset: commentsByAuthor()[aid]?.length || 0
|
offset: commentsByAuthor()[aid]?.length || 0
|
||||||
})
|
})
|
||||||
const result = await authorCommentsFetcher()
|
const result = await authorCommentsFetcher()
|
||||||
result && addReactions(result)
|
result && addShoutReactions(result)
|
||||||
restoreScrollPosition()
|
restoreScrollPosition()
|
||||||
return result as LoadMoreItems
|
return result as LoadMoreItems
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Meta, Title } from '@solidjs/meta'
|
||||||
import { useLocation } from '@solidjs/router'
|
import { useLocation } from '@solidjs/router'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import type { JSX } from 'solid-js'
|
import type { JSX } from 'solid-js'
|
||||||
import { Show, createEffect, createMemo, createSignal } from 'solid-js'
|
import { Show, createMemo } from 'solid-js'
|
||||||
import { useLocalize } from '~/context/localize'
|
import { useLocalize } from '~/context/localize'
|
||||||
import { Shout } from '~/graphql/schema/core.gen'
|
import { Shout } from '~/graphql/schema/core.gen'
|
||||||
import enKeywords from '~/intl/locales/en/keywords.json'
|
import enKeywords from '~/intl/locales/en/keywords.json'
|
||||||
|
@ -27,7 +27,6 @@ type PageLayoutProps = {
|
||||||
class?: string
|
class?: string
|
||||||
withPadding?: boolean
|
withPadding?: boolean
|
||||||
zeroBottomPadding?: boolean
|
zeroBottomPadding?: boolean
|
||||||
scrollToComments?: (value: boolean) => void
|
|
||||||
key?: string
|
key?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,12 +47,10 @@ export const PageLayout = (props: PageLayoutProps) => {
|
||||||
: imageUrl
|
: imageUrl
|
||||||
)
|
)
|
||||||
const description = createMemo(() => props.desc || (props.article && descFromBody(props.article.body)))
|
const description = createMemo(() => props.desc || (props.article && descFromBody(props.article.body)))
|
||||||
const keypath = createMemo(() => (props.key || loc?.pathname.split('/')[0]) as keyof typeof ruKeywords)
|
const keywords = createMemo(() => {
|
||||||
const keywords = createMemo(
|
const keypath = (props.key || loc?.pathname.split('/')[0]) as keyof typeof ruKeywords
|
||||||
() => props.keywords || (lang() === 'ru' ? ruKeywords[keypath()] : enKeywords[keypath()])
|
return props.keywords || lang() === 'ru' ? ruKeywords[keypath] : enKeywords[keypath]
|
||||||
)
|
})
|
||||||
const [scrollToComments, setScrollToComments] = createSignal<boolean>(false)
|
|
||||||
createEffect(() => props.scrollToComments?.(scrollToComments()))
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Title>{props.article?.title || t(props.title)}</Title>
|
<Title>{props.article?.title || t(props.title)}</Title>
|
||||||
|
@ -63,7 +60,6 @@ export const PageLayout = (props: PageLayoutProps) => {
|
||||||
desc={props.desc}
|
desc={props.desc}
|
||||||
cover={imageUrl}
|
cover={imageUrl}
|
||||||
isHeaderFixed={isHeaderFixed}
|
isHeaderFixed={isHeaderFixed}
|
||||||
scrollToComments={(value) => setScrollToComments(value)}
|
|
||||||
/>
|
/>
|
||||||
<Meta name="descprition" content={description() || ''} />
|
<Meta name="descprition" content={description() || ''} />
|
||||||
<Meta name="keywords" content={keywords()} />
|
<Meta name="keywords" content={keywords()} />
|
||||||
|
|
|
@ -24,10 +24,10 @@ type ReactionsContextType = {
|
||||||
reactionsByShout: Record<number, Reaction[]>
|
reactionsByShout: Record<number, Reaction[]>
|
||||||
commentsByAuthor: Accessor<Record<number, Reaction[]>>
|
commentsByAuthor: Accessor<Record<number, Reaction[]>>
|
||||||
loadReactionsBy: (args: QueryLoad_Reactions_ByArgs) => Promise<Reaction[]>
|
loadReactionsBy: (args: QueryLoad_Reactions_ByArgs) => Promise<Reaction[]>
|
||||||
createReaction: (reaction: MutationCreate_ReactionArgs) => Promise<void>
|
createShoutReaction: (reaction: MutationCreate_ReactionArgs) => Promise<void>
|
||||||
updateReaction: (reaction: MutationUpdate_ReactionArgs) => Promise<Reaction>
|
updateShoutReaction: (reaction: MutationUpdate_ReactionArgs) => Promise<Reaction>
|
||||||
deleteReaction: (id: number) => Promise<{ error: string } | null>
|
deleteShoutReaction: (id: number) => Promise<{ error: string } | null>
|
||||||
addReactions: (rrr: Reaction[]) => void
|
addShoutReactions: (rrr: Reaction[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReactionsContext = createContext<ReactionsContextType>({} as ReactionsContextType)
|
const ReactionsContext = createContext<ReactionsContextType>({} as ReactionsContextType)
|
||||||
|
@ -46,7 +46,7 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
|
||||||
const { session } = useSession()
|
const { session } = useSession()
|
||||||
const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token))
|
const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token))
|
||||||
|
|
||||||
const addReactions = (rrr: Reaction[]) => {
|
const addShoutReactions = (rrr: Reaction[]) => {
|
||||||
const newReactionsByShout: Record<number, Reaction[]> = { ...reactionsByShout }
|
const newReactionsByShout: Record<number, Reaction[]> = { ...reactionsByShout }
|
||||||
const newReactionsByAuthor: Record<number, Reaction[]> = { ...reactionsByAuthor }
|
const newReactionsByAuthor: Record<number, Reaction[]> = { ...reactionsByAuthor }
|
||||||
const newReactionEntities = rrr.reduce(
|
const newReactionEntities = rrr.reduce(
|
||||||
|
@ -80,18 +80,16 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
|
||||||
const fetcher = await loadReactions(opts)
|
const fetcher = await loadReactions(opts)
|
||||||
const result = (await fetcher()) || []
|
const result = (await fetcher()) || []
|
||||||
console.debug('[context.reactions] loaded', result)
|
console.debug('[context.reactions] loaded', result)
|
||||||
result && addReactions(result)
|
result && addShoutReactions(result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
const createReaction = async (input: MutationCreate_ReactionArgs): Promise<void> => {
|
const createShoutReaction = async (input: MutationCreate_ReactionArgs): Promise<void> => {
|
||||||
const resp = await client()?.mutation(createReactionMutation, input).toPromise()
|
const resp = await client()?.mutation(createReactionMutation, input).toPromise()
|
||||||
const { error, reaction } = resp?.data?.create_reaction || {}
|
const { error, reaction } = resp?.data?.create_reaction || {}
|
||||||
if (error) await showSnackbar({ type: 'error', body: t(error) })
|
if (error) await showSnackbar({ type: 'error', body: t(error) })
|
||||||
if (!reaction) return
|
if (!reaction) return
|
||||||
const changes = {
|
const changes = { [reaction.id]: reaction }
|
||||||
[reaction.id]: reaction
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([ReactionKind.Like, ReactionKind.Dislike].includes(reaction.kind)) {
|
if ([ReactionKind.Like, ReactionKind.Dislike].includes(reaction.kind)) {
|
||||||
const oppositeReactionKind =
|
const oppositeReactionKind =
|
||||||
|
@ -110,10 +108,11 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setReactionEntities(changes)
|
addShoutReactions([reaction])
|
||||||
|
return reaction
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteReaction = async (
|
const deleteShoutReaction = async (
|
||||||
reaction_id: number
|
reaction_id: number
|
||||||
): Promise<{ error: string; reaction?: string } | null> => {
|
): Promise<{ error: string; reaction?: string } | null> => {
|
||||||
if (reaction_id) {
|
if (reaction_id) {
|
||||||
|
@ -129,7 +128,7 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateReaction = async (input: MutationUpdate_ReactionArgs): Promise<Reaction> => {
|
const updateShoutReaction = async (input: MutationUpdate_ReactionArgs): Promise<Reaction> => {
|
||||||
const resp = await client()?.mutation(updateReactionMutation, input).toPromise()
|
const resp = await client()?.mutation(updateReactionMutation, input).toPromise()
|
||||||
const result = resp?.data?.update_reaction
|
const result = resp?.data?.update_reaction
|
||||||
if (!result) throw new Error('cannot update reaction')
|
if (!result) throw new Error('cannot update reaction')
|
||||||
|
@ -143,10 +142,10 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
loadReactionsBy,
|
loadReactionsBy,
|
||||||
createReaction,
|
createShoutReaction,
|
||||||
updateReaction,
|
updateShoutReaction,
|
||||||
deleteReaction,
|
deleteShoutReaction,
|
||||||
addReactions
|
addShoutReactions
|
||||||
}
|
}
|
||||||
|
|
||||||
const value: ReactionsContextType = { reactionEntities, reactionsByShout, commentsByAuthor, ...actions }
|
const value: ReactionsContextType = { reactionEntities, reactionsByShout, commentsByAuthor, ...actions }
|
||||||
|
|
|
@ -28,7 +28,12 @@ export const route: RouteDefinition = {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ArticlePageProps = { article?: Shout; comments?: Reaction[]; votes?: Reaction[]; author?: Author }
|
export type ArticlePageProps = {
|
||||||
|
article?: Shout
|
||||||
|
comments?: Reaction[]
|
||||||
|
votes?: Reaction[]
|
||||||
|
author?: Author
|
||||||
|
}
|
||||||
|
|
||||||
export type SlugPageProps = {
|
export type SlugPageProps = {
|
||||||
article?: Shout
|
article?: Shout
|
||||||
|
@ -125,4 +130,3 @@ export default function ArticlePage(props: RouteSectionProps<SlugPageProps>) {
|
||||||
}
|
}
|
||||||
return <ArticlePage {...props} />
|
return <ArticlePage {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
import ArticlePage from "~/routes/[slug]/[...tab]"
|
import ArticlePage from '~/routes/[slug]/[...tab]'
|
||||||
|
|
||||||
export default ArticlePage
|
export default ArticlePage
|
||||||
|
|
Loading…
Reference in New Issue
Block a user