diff --git a/package.json b/package.json index 367bf66..354ad4a 100644 --- a/package.json +++ b/package.json @@ -31,13 +31,13 @@ "@trpc/server": "^10.43.6", "@tryfabric/mack": "^1.2.1", "class-variance-authority": "^0.7.0", + "client-only": "^0.0.1", "cloudinary": "^1.41.0", "clsx": "^2.0.0", "cmdk": "^0.2.0", "date-fns": "^2.30.0", "gemoji": "^8.1.0", "isomorphic-dompurify": "^1.10.0", - "jsdom": "^23.0.1", "marked": "^11.0.0", "match-sorter": "^6.3.1", "next": "^14.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fc1bd32..335d131 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,9 @@ dependencies: class-variance-authority: specifier: ^0.7.0 version: 0.7.0 + client-only: + specifier: ^0.0.1 + version: 0.0.1 cloudinary: specifier: ^1.41.0 version: 1.41.0 @@ -74,9 +77,6 @@ dependencies: isomorphic-dompurify: specifier: ^1.10.0 version: 1.10.0 - jsdom: - specifier: ^23.0.1 - version: 23.0.1 marked: specifier: ^11.0.0 version: 11.0.0 diff --git a/src/app/(default)/layout.tsx b/src/app/(default)/layout.tsx index f7c3c41..0883c13 100644 --- a/src/app/(default)/layout.tsx +++ b/src/app/(default)/layout.tsx @@ -1,25 +1,19 @@ import { type ReactNode } from 'react' import { Header } from '../_components/header' import { Footer } from '../_components/footer' -import { AuthProvider } from '../_providers/auth' -import { getServerAuthSession } from '~/server/auth' export default async function DefaultLayout({ children, }: { children: ReactNode }) { - const session = await getServerAuthSession() - return ( - -
-
- {children} -
-
-
+
+
+ {children} +
+
- +
) } diff --git a/src/app/(default)/page.tsx b/src/app/(default)/page.tsx index 916cd7a..18b4e49 100644 --- a/src/app/(default)/page.tsx +++ b/src/app/(default)/page.tsx @@ -1,7 +1,6 @@ import { api } from '~/trpc/server' -import { PostSummary } from '../_components/post-summary' -import { getServerAuthSession } from '~/server/auth' -import { Pagination } from '../_components/pagination' +import { PostFeed } from '../_components/post-feed' +import { Suspense, cache } from 'react' const POSTS_PER_PAGE = 20 @@ -12,37 +11,32 @@ export default async function Index({ }) { const currentPageNumber = searchParams.page ? Number(searchParams.page) : 1 - const { posts, postCount } = await api.post.feed.query({ - take: POSTS_PER_PAGE, - skip: - currentPageNumber === 1 - ? undefined - : POSTS_PER_PAGE * (currentPageNumber - 1), + const cachedData = cache(async () => { + return await api.post.feed.query({ + take: POSTS_PER_PAGE, + skip: + currentPageNumber === 1 + ? undefined + : POSTS_PER_PAGE * (currentPageNumber - 1), + }) }) - const session = await getServerAuthSession() + + const initialPostData = await cachedData() return ( <> - {!postCount ? ( + {!initialPostData.postCount ? (
There are no published posts to show yet.
) : ( -
-
    - {posts.map((post) => ( -
  • - -
  • - ))} -
-
+ + + )} - ) } diff --git a/src/app/(default)/post/[id]/_components/edit-post-form.tsx b/src/app/(default)/post/[id]/_components/edit-post-form.tsx index bf60fd9..71a59e7 100644 --- a/src/app/(default)/post/[id]/_components/edit-post-form.tsx +++ b/src/app/(default)/post/[id]/_components/edit-post-form.tsx @@ -7,8 +7,10 @@ import { TextField } from '~/app/_components/text-field' import { Button } from '~/app/_components/button' import MarkdownIcon from '~/app/_svg/markdown-icon' import { MarkdownEditor } from '~/app/_components/markdown-editor' -import { useRouter } from 'next/navigation' + import { api } from '~/trpc/react' +import { useRouter } from 'next/navigation' +import { type RouterOutputs } from '~/trpc/shared' type FormData = { title: string @@ -17,25 +19,32 @@ type FormData = { type PostFormProps = { postId: number - defaultValues?: FormData isSubmitting?: boolean backTo: string + initialData: RouterOutputs['post']['detail'] } export const EditPostForm = ({ postId, - defaultValues, backTo, + initialData, }: PostFormProps) => { + const { data } = api.post.detail.useQuery( + { + id: postId, + }, + { initialData }, + ) + const { control, register, handleSubmit } = useForm({ - defaultValues, + defaultValues: data, }) // useLeaveConfirm({ formState }) - const router = useRouter() + const editPostMutation = api.post.edit.useMutation({ - onSuccess: () => { + onSuccess: async () => { router.push(`/post/${postId}`) }, }) diff --git a/src/app/(default)/post/[id]/edit/page.tsx b/src/app/(default)/post/[id]/edit/page.tsx index 8f8c72e..f835af7 100644 --- a/src/app/(default)/post/[id]/edit/page.tsx +++ b/src/app/(default)/post/[id]/edit/page.tsx @@ -2,6 +2,7 @@ import { getServerAuthSession } from '~/server/auth' import { api } from '~/trpc/server' import { EditPostForm } from '../_components/edit-post-form' +import { cache } from 'react' type ProfilePageParams = { params: { @@ -10,10 +11,14 @@ type ProfilePageParams = { } export const generateMetadata = async ({ params }: ProfilePageParams) => { - const post = await api.post.detail.query({ - id: Number(params.id), + const cachedPostData = cache(async () => { + return await api.post.detail.query({ + id: Number(params.id), + }) }) + const post = await cachedPostData() + if (!post) return return { @@ -22,10 +27,14 @@ export const generateMetadata = async ({ params }: ProfilePageParams) => { } export default async function EditPostPage({ params }: ProfilePageParams) { - const post = await api.post.detail.query({ - id: Number(params.id), + const cachedPostData = cache(async () => { + return await api.post.detail.query({ + id: Number(params.id), + }) }) + const post = await cachedPostData() + const session = await getServerAuthSession() const postBelongsToUser = post.author.id === session!.user.id @@ -37,14 +46,7 @@ export default async function EditPostPage({ params }: ProfilePageParams) { Edit "{post.title}"
- +
) : ( diff --git a/src/app/(default)/post/[id]/page.tsx b/src/app/(default)/post/[id]/page.tsx index f575be8..5e413c0 100644 --- a/src/app/(default)/post/[id]/page.tsx +++ b/src/app/(default)/post/[id]/page.tsx @@ -1,17 +1,11 @@ -import { AuthorWithDate } from '~/app/_components/author-with-date' import { Avatar } from '~/app/_components/avatar' -import { Banner } from '~/app/_components/banner' -import { Button } from '~/app/_components/button' -import { Comment, AddCommentForm } from '~/app/_components/comment' - -import { HtmlView } from '~/app/_components/html-view' -import { ReactionButton } from '~/app/_components/like-button' -import MessageIcon from '~/app/_svg/message-icon' +import { Comment, AddCommentForm } from '~/app/_components/comment' import { getServerAuthSession } from '~/server/auth' import { api } from '~/trpc/server' -import { PostAction } from './_components/post-action' +import { PostView } from '~/app/_components/post-view' +import { cache } from 'react' type PostPageParams = { params: { @@ -20,10 +14,13 @@ type PostPageParams = { } export const generateMetadata = async ({ params }: PostPageParams) => { - const post = await api.post.detail.query({ - id: Number(params.id), + const cachedPost = cache(async () => { + return await api.post.detail.query({ + id: Number(params.id), + }) }) + const post = await cachedPost() if (!post) return return { @@ -32,55 +29,19 @@ export const generateMetadata = async ({ params }: PostPageParams) => { } export default async function PostPage({ params }: PostPageParams) { - const post = await api.post.detail.query({ - id: Number(params.id), + const cachedPost = cache(async () => { + return await api.post.detail.query({ + id: Number(params.id), + }) }) + const post = await cachedPost() + const session = await getServerAuthSession() - const isUserAdmin = session!.user.role === 'ADMIN' - const postBelongsToUser = post.author.id === session!.user.id return (
-
- {post.hidden && ( - - This post has been hidden and is only visible to administrators. - - )} - -
-

- {post.title} -

- {(postBelongsToUser || isUserAdmin) && ( - <> - - - )} -
-
- -
- -
- - -
-
+
{post.comments.length > 0 && ( diff --git a/src/app/(default)/profile/[userId]/_components/edit-profile.tsx b/src/app/(default)/profile/[userId]/_components/edit-profile.tsx index d6426ae..2a793ab 100644 --- a/src/app/(default)/profile/[userId]/_components/edit-profile.tsx +++ b/src/app/(default)/profile/[userId]/_components/edit-profile.tsx @@ -1,6 +1,5 @@ 'use client' -import { useRouter } from 'next/navigation' import { type SubmitHandler, useForm } from 'react-hook-form' import { toast } from 'react-hot-toast' import { Button } from '~/app/_components/button' @@ -16,6 +15,10 @@ import { TextField } from '~/app/_components/text-field' import { useDialogStore } from '~/app/_hooks/use-dialog-store' import EditIcon from '~/app/_svg/edit-icon' import { api } from '~/trpc/react' +import { type RouterOutputs } from '~/trpc/shared' +import { env } from '~/env' +import { UpdateAvatarAction } from './update-avatar' +import { Avatar } from '~/app/_components/avatar' type EditFormData = { name: string @@ -25,23 +28,33 @@ type EditFormData = { const EditProfileDialog = ({ user, }: { - user: { - name: string - title: string | null - } + user: RouterOutputs['user']['profile'] }) => { const { handleDialogClose } = useDialogStore() + const utils = api.useUtils() + const { register, handleSubmit, reset } = useForm({ defaultValues: { - name: user.name, - title: user.title, + name: user.name ?? '', + title: user.title ?? '', }, }) - const router = useRouter() const editUserMutation = api.user.edit.useMutation({ - onSuccess: () => { - router.refresh() + onMutate: (data) => { + utils.user.profile.setData( + { id: user.id }, + { + name: data.name, + id: user.id, + title: data.title ?? '', + image: user.image, + }, + ) + }, + onSuccess: async () => { + await utils.post.feed.invalidate({ authorId: user.id }) + await utils.user.profile.invalidate({ id: user.id }) }, onError: (error) => { toast.error(`Something went wrong: ${error.message}`) @@ -56,8 +69,7 @@ const EditProfileDialog = ({ const onSubmit: SubmitHandler = (data) => { editUserMutation.mutate( { - name: data.name, - title: data.title, + ...data, }, { onSuccess: () => handleDialogClose(), @@ -102,25 +114,50 @@ const EditProfileDialog = ({ export const EditProfileAction = ({ user, + profileBelongsToUser, }: { - user: { - name: string - title: string | null - } + user: RouterOutputs['user']['profile'] + profileBelongsToUser: boolean }) => { const { handleDialog } = useDialogStore() + const { data } = api.user.profile.useQuery( + { id: user.id }, + { + initialData: user, + }, + ) return ( -
- -
+ <> +
+ {env.NEXT_PUBLIC_ENABLE_IMAGE_UPLOAD && profileBelongsToUser ? ( + + ) : ( + + )} + +
+

+ {data.name} +

+ {data.title && ( +

+ {data.title} +

+ )} +
+
+
+ +
+ ) } diff --git a/src/app/(default)/profile/[userId]/page.tsx b/src/app/(default)/profile/[userId]/page.tsx index 23f0fe5..eb577f8 100644 --- a/src/app/(default)/profile/[userId]/page.tsx +++ b/src/app/(default)/profile/[userId]/page.tsx @@ -1,14 +1,10 @@ -import { Avatar } from '~/app/_components/avatar' - -import { Pagination } from '~/app/_components/pagination' -import { PostSummary } from '~/app/_components/post-summary' import DotPattern from '~/app/_svg/dot-pattern' -import { env } from '~/env' import { getServerAuthSession } from '~/server/auth' import { api } from '~/trpc/server' import { EditProfileAction } from './_components/edit-profile' -import { UpdateAvatarAction } from './_components/update-avatar' +import { PostFeed } from '~/app/_components/post-feed' +import { Suspense, cache } from 'react' type ProfilePageParams = { params: { @@ -18,10 +14,14 @@ type ProfilePageParams = { } export const generateMetadata = async ({ params }: ProfilePageParams) => { - const profile = await api.user.profile.query({ - id: params.userId, + const profileData = cache(async () => { + return await api.user.profile.query({ + id: params.userId, + }) }) + const profile = await profileData() + if (!profile) return return { @@ -37,18 +37,27 @@ export default async function ProfilePage({ }: ProfilePageParams) { const currentPageNumber = searchParams.page ? Number(searchParams.page) : 1 - const profile = await api.user.profile.query({ - id: params.userId, + const profileData = cache(async () => { + return await api.user.profile.query({ + id: params.userId, + }) }) - const { posts, postCount } = await api.post.feed.query({ - take: POSTS_PER_PAGE, - skip: - currentPageNumber === 1 - ? undefined - : POSTS_PER_PAGE * (currentPageNumber - 1), - authorId: params.userId, + + const profile = await profileData() + + const cachedData = cache(async () => { + return await api.post.feed.query({ + take: POSTS_PER_PAGE, + skip: + currentPageNumber === 1 + ? undefined + : POSTS_PER_PAGE * (currentPageNumber - 1), + authorId: params.userId, + }) }) + const initialPostData = await cachedData() + const session = await getServerAuthSession() if (!profile) return @@ -58,58 +67,31 @@ export default async function ProfilePage({ return ( <>
-
- {env.NEXT_PUBLIC_ENABLE_IMAGE_UPLOAD && profileBelongsToUser ? ( - - ) : ( - - )} - -
-

- {profile.name} -

- {profile.title && ( -

- {profile.title} -

- )} -
-
- {profileBelongsToUser && ( )}
-
- {postCount === 0 ? ( +
+ {initialPostData.postCount === 0 ? (
This user hasn't published any posts yet.
) : ( -
    - {posts.map((post) => ( -
  • - -
  • - ))} -
+ + + )}
- - ) } diff --git a/src/app/_components/button.tsx b/src/app/_components/button.tsx index ec1e8b8..9270ece 100644 --- a/src/app/_components/button.tsx +++ b/src/app/_components/button.tsx @@ -1,4 +1,9 @@ -import { type ComponentPropsWithRef, type ReactNode } from 'react' +import { + forwardRef, + type ComponentPropsWithRef, + type ReactNode, + type Ref, +} from 'react' import { cva, type VariantProps } from 'class-variance-authority' import Link, { type LinkProps } from 'next/link' @@ -41,39 +46,47 @@ type BaseProps = export type ButtonVariant = VariantProps type ButtonProps = BaseProps & ButtonVariant -export const Button = ({ - children, - variant, - className, - disabled, - isLoading, - loadingChildren, - ...props -}: ButtonProps) => { - if (props.href === undefined) { +export const Button = forwardRef( + ( + { + children, + variant, + className, + disabled, + isLoading, + loadingChildren, + ...props + }: ButtonProps, + ref: Ref, + ) => { + if (props.href === undefined) { + return ( + + ) + } + return ( - + ) - } + }, +) - return ( - - {isLoading && } - {isLoading && loadingChildren ? loadingChildren : children} - - ) -} +Button.displayName = 'Button' diff --git a/src/app/_components/liked-by.tsx b/src/app/_components/liked-by.tsx index aeb0a53..6de8f2b 100644 --- a/src/app/_components/liked-by.tsx +++ b/src/app/_components/liked-by.tsx @@ -3,7 +3,7 @@ import { classNames } from '~/utils/core' import * as Tooltip from '@radix-ui/react-tooltip' import { type RouterOutputs } from '~/trpc/shared' -import { MAX_LIKED_BY_SHOWN } from './like-button' +import { MAX_LIKED_BY_SHOWN } from './reaction-button' import { type ReactNode } from 'react' type LikedByProps = { @@ -24,6 +24,7 @@ export const LikedBy = ({ trigger, likedBy }: LikedByProps) => { onMouseDown={(event) => { event.preventDefault() }} + asChild > {trigger} diff --git a/src/app/_components/post-feed.tsx b/src/app/_components/post-feed.tsx new file mode 100644 index 0000000..78ed11c --- /dev/null +++ b/src/app/_components/post-feed.tsx @@ -0,0 +1,54 @@ +'use client' + +import { useSearchParams } from 'next/navigation' +import { api } from '~/trpc/react' +import { PostSummary } from './post-summary' +import { type RouterOutputs } from '~/trpc/shared' +import { Pagination } from './pagination' + +type PostFeedProps = { + postsPerPage: number + initialPosts: RouterOutputs['post']['feed'] + authorId?: string +} + +export const PostFeed = ({ + postsPerPage, + initialPosts, + authorId, +}: PostFeedProps) => { + const searchParams = useSearchParams() + const currentPageNumber = searchParams.get('page') + ? Number(searchParams.get('page')) + : 1 + + const { data } = api.post.feed.useQuery( + { + take: postsPerPage, + skip: + currentPageNumber === 1 + ? undefined + : postsPerPage * (currentPageNumber - 1), + authorId, + }, + { initialData: initialPosts }, + ) + + return ( + <> +
    + {data.posts.map((post) => ( +
  • + +
  • + ))} +
+ + + + ) +} diff --git a/src/app/_components/post-summary.tsx b/src/app/_components/post-summary.tsx index b067e77..1ea500f 100644 --- a/src/app/_components/post-summary.tsx +++ b/src/app/_components/post-summary.tsx @@ -9,14 +9,11 @@ import { HtmlView } from '~/app/_components/html-view' import ChevronRightIcon from '~/app/_svg/chevron-right-icon' import MessageIcon from '~/app/_svg/message-icon' -import { type Session } from 'next-auth' -import { LikedBy } from './liked-by' -import HeartFilledIcon from '../_svg/heart-filled-icon' -import HeartIcon from '../_svg/heart-icon' +import { ReactionButton } from './reaction-button' +import { Suspense } from 'react' export type PostSummaryProps = { post: RouterOutputs['post']['feed']['posts'][number] - session: Session | null hideAuthor?: boolean } @@ -66,21 +63,14 @@ export const PostSummary = ({ post, hideAuthor }: PostSummaryProps) => { )}
- - {post.isLikedByCurrentUser ? ( - - ) : ( - - )} - - {post.likedBy.length} - -
- } - likedBy={post.likedBy} - /> + + + { + const { data } = api.post.detail.useQuery( + { + id: Number(postId), + }, + { initialData: initialPostData }, + ) + + const session = useSession() + if (!data) return null + + const postBelongsToUser = data.author.id === session.data?.user?.id + const isUserAdmin = session.data?.user.role === 'ADMIN' + + return ( +
+ {data.hidden && ( + + This post has been hidden and is only visible to administrators. + + )} + +
+

+ {data.title} +

+ {(postBelongsToUser || isUserAdmin) && ( + <> + + + )} +
+
+ +
+ +
+ + + + +
+
+ ) +} diff --git a/src/app/_components/like-button.tsx b/src/app/_components/reaction-button.tsx similarity index 62% rename from src/app/_components/like-button.tsx rename to src/app/_components/reaction-button.tsx index 8449535..c143b76 100644 --- a/src/app/_components/like-button.tsx +++ b/src/app/_components/reaction-button.tsx @@ -6,50 +6,100 @@ import HeartFilledIcon from '../_svg/heart-filled-icon' import HeartIcon from '../_svg/heart-icon' import { api } from '~/trpc/react' -import { useRouter } from 'next/navigation' import { useState } from 'react' import { LikedBy } from './liked-by' import { type RouterOutputs } from '~/trpc/shared' +import { useSearchParams } from 'next/navigation' export const MAX_LIKED_BY_SHOWN = 50 -type LikeButtonProps = { +type ReactionButtonProps = { likeCount: number isLikedByCurrentUser: boolean likedBy: RouterOutputs['post']['detail']['likedBy'] id: number } +const POSTS_PER_PAGE = 20 + export const ReactionButton = ({ id, likeCount, likedBy, isLikedByCurrentUser, -}: LikeButtonProps) => { +}: ReactionButtonProps) => { const [isAnimating, setIsAnimating] = useState(false) - const router = useRouter() + const utils = api.useUtils() + const params = useSearchParams() + + const currentPageNumber = params.get('page') ? Number(params.get('page')) : 1 + + const queryParams = { + take: 20, + skip: + currentPageNumber === 1 + ? undefined + : POSTS_PER_PAGE * (currentPageNumber - 1), + } + + const previousPosts = utils.post.feed.getData(queryParams) + + const handleAnimationEnd = () => { + const timeout = setTimeout(() => { + setIsAnimating(false) + }, 1000) + + return () => clearTimeout(timeout) + } + const like = api.post.like.useMutation({ - onSuccess: () => { - router.refresh() - const timeout = setTimeout(() => { - setIsAnimating(false) - }, 1000) + onMutate: async (data) => { + const newPosts = previousPosts!.posts.map((post) => { + if (post.id === data.id) { + return { + ...post, + isLikedByCurrentUser: true, + likedBy: [...post.likedBy, 'You'], + } + } + return post + }) - return () => clearTimeout(timeout) + utils.post.feed.setData(queryParams, { + posts: newPosts, + postCount: newPosts.length, + }) + + setIsAnimating(true) + handleAnimationEnd() }, }) + const unlike = api.post.unlike.useMutation({ - onSuccess: () => { - router.refresh() + onMutate: async (data) => { + const newPosts = previousPosts!.posts.map((post) => { + if (post.id === data.id) { + return { + ...post, + isLikedByCurrentUser: false, + likedBy: post.likedBy.filter((user) => user !== 'You'), + } + } + return post + }) + + utils.post.feed.setData(queryParams, { + posts: newPosts, + postCount: newPosts.length, + }) }, }) - const handleReaction = async () => { + const handleReaction = () => { if (isLikedByCurrentUser) { - await unlike.mutateAsync({ id }) + unlike.mutate({ id }) } else { - setIsAnimating(true) - await like.mutateAsync({ id }) + like.mutate({ id }) } } @@ -68,7 +118,7 @@ export const ReactionButton = ({ }, )} onClick={handleReaction} - disabled={like.isLoading} + disabled={like.isLoading || unlike.isLoading || isAnimating} > {isLikedByCurrentUser && !isAnimating ? ( diff --git a/src/app/_providers/auth.tsx b/src/app/_providers/auth.tsx index 7b53466..f1776d8 100644 --- a/src/app/_providers/auth.tsx +++ b/src/app/_providers/auth.tsx @@ -1,24 +1,3 @@ 'use client' -import { type Session } from 'next-auth' -import { signIn } from 'next-auth/react' -import { type ReactNode, useEffect } from 'react' - -export const AuthProvider = ({ - session, - children, -}: { - session: Session | null - children: ReactNode -}) => { - const isUser = !!session?.user - useEffect(() => { - if (!isUser) void signIn() - }, [isUser]) - - if (isUser) { - return <>{children} - } - - return null -} +export { SessionProvider } from 'next-auth/react' diff --git a/src/app/layout.tsx b/src/app/layout.tsx index be41789..8198132 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -10,6 +10,8 @@ import { Toaster } from '~/app/_providers/toaster' import { SearchDialog } from './_components/search-dialog' import { AlertDialog } from './_components/alert-dialog' +import { getServerAuthSession } from '~/server/auth' +import { SessionProvider } from './_providers/auth' const inter = localFont({ variable: '--font-sans', @@ -37,18 +39,22 @@ export default async function RootLayout({ }: { children: React.ReactNode }) { + const session = await getServerAuthSession() + return ( - - -
{children}
+ + + +
{children}
- - - -
-
+ + + +
+
+ ) diff --git a/src/server/api/routers/post.ts b/src/server/api/routers/post.ts index e523105..0b7ee46 100644 --- a/src/server/api/routers/post.ts +++ b/src/server/api/routers/post.ts @@ -202,6 +202,8 @@ export const postRouter = createTRPCRouter({ }, }) + revalidatePath('/', 'page') + await postToSlackIfEnabled({ post, authorName: ctx.session.user.name }) return post diff --git a/src/server/summary.ts b/src/server/summary.ts index 178704f..d595fcf 100644 --- a/src/server/summary.ts +++ b/src/server/summary.ts @@ -1,16 +1,15 @@ -import { JSDOM } from 'jsdom' +import 'client-only' -export const summarize = ( - html: string, -): { summary: string; hasMore: boolean } => { - const document = new JSDOM(html) +export const summarize = (html: string) => { + const parser = new DOMParser() + const document = parser.parseFromString(html, 'text/html') const allowedTags = ['p', 'ul', 'ol', 'h3', 'pre', 'img'] let firstElement for (const tag of allowedTags) { - firstElement = document.window.document.querySelector(tag) + firstElement = document.querySelector(tag) if (firstElement) { break } @@ -25,12 +24,12 @@ export const summarize = ( return { summary: firstElement.outerHTML + firstElement.nextElementSibling.outerHTML, - hasMore: document.window.document.body.children.length > 2, + hasMore: document.body.children.length > 2, } } else { return { summary: firstElement.outerHTML, - hasMore: document.window.document.body.children.length > 1, + hasMore: document.body.children.length > 1, } } } else { diff --git a/src/trpc/server.ts b/src/trpc/server.ts index 0af2a6e..885ce48 100644 --- a/src/trpc/server.ts +++ b/src/trpc/server.ts @@ -1,19 +1,19 @@ -import "server-only"; +import 'server-only' import { createTRPCProxyClient, loggerLink, TRPCClientError, -} from "@trpc/client"; -import { callProcedure } from "@trpc/server"; -import { observable } from "@trpc/server/observable"; -import { type TRPCErrorResponse } from "@trpc/server/rpc"; -import { cookies } from "next/headers"; -import { cache } from "react"; +} from '@trpc/client' +import { callProcedure } from '@trpc/server' +import { observable } from '@trpc/server/observable' +import { type TRPCErrorResponse } from '@trpc/server/rpc' +import { cookies } from 'next/headers' +import { cache } from 'react' -import { appRouter } from "~/server/api/root"; -import { createTRPCContext } from "~/server/api/trpc"; -import { transformer } from "./shared"; +import { appRouter, type AppRouter } from '~/server/api/root' +import { createTRPCContext } from '~/server/api/trpc' +import { transformer } from './shared' /** * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when @@ -23,18 +23,18 @@ const createContext = cache(() => { return createTRPCContext({ headers: new Headers({ cookie: cookies().toString(), - "x-trpc-source": "rsc", + 'x-trpc-source': 'rsc', }), - }); -}); + }) +}) -export const api = createTRPCProxyClient({ +export const api = createTRPCProxyClient({ transformer, links: [ loggerLink({ enabled: (op) => - process.env.NODE_ENV === "development" || - (op.direction === "down" && op.result instanceof Error), + process.env.NODE_ENV === 'development' || + (op.direction === 'down' && op.result instanceof Error), }), /** * Custom RSC link that lets us invoke procedures without using http requests. Since Server @@ -51,15 +51,15 @@ export const api = createTRPCProxyClient({ rawInput: op.input, ctx, type: op.type, - }); + }) }) .then((data) => { - observer.next({ result: { data } }); - observer.complete(); + observer.next({ result: { data } }) + observer.complete() }) .catch((cause: TRPCErrorResponse) => { - observer.error(TRPCClientError.from(cause)); - }); + observer.error(TRPCClientError.from(cause)) + }) }), ], -}); +})