scrollto+shoutreaction

This commit is contained in:
Untone 2024-09-06 08:13:24 +03:00
parent 1ec368eae7
commit 260b95f692
12 changed files with 62 additions and 83 deletions

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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 = () => {

View File

@ -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>

View File

@ -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
} }

View File

@ -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()} />

View File

@ -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 }

View File

@ -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} />
} }

View File

@ -1,3 +1,3 @@
import ArticlePage from "~/routes/[slug]/[...tab]" import ArticlePage from '~/routes/[slug]/[...tab]'
export default ArticlePage export default ArticlePage