From f6040053dd847b5b06a83a1cf4d755812987aeac Mon Sep 17 00:00:00 2001 From: NiallJoeMaher Date: Fri, 7 Jun 2024 20:11:47 +0100 Subject: [PATCH 1/2] Add preview feature for articles --- app/(app)/draft/[id]/page.tsx | 81 +++++++++++++++++++++++++++++++++++ app/robots.ts | 1 + server/lib/posts.ts | 60 +++++++++++++++++++++++--- 3 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 app/(app)/draft/[id]/page.tsx diff --git a/app/(app)/draft/[id]/page.tsx b/app/(app)/draft/[id]/page.tsx new file mode 100644 index 000000000..6279244e7 --- /dev/null +++ b/app/(app)/draft/[id]/page.tsx @@ -0,0 +1,81 @@ +import React from "react"; +import Markdoc from "@markdoc/markdoc"; +import Link from "next/link"; +import BioBar from "@/components/BioBar/BioBar"; +import { markdocComponents } from "@/markdoc/components"; +import { config } from "@/markdoc/config"; +import { headers } from "next/headers"; +import { notFound } from "next/navigation"; + +import { type Metadata } from "next"; +import { getPostPreview } from "@/server/lib/posts"; +import { getCamelCaseFromLower } from "@/utils/utils"; + +type Props = { params: { id: string } }; + +export async function generateMetadata({ params }: Props): Promise { + const { id } = params; + + const post = await getPostPreview({ id }); + + if (!post) return {}; + const host = headers().get("host") || ""; + return { + title: `Previewing ${post.title} | by ${post.user.name} | Codú`, + authors: { + name: post.user.name, + url: `https://www.${host}/${post.user.username}`, + }, + description: post.excerpt, + alternates: { + canonical: post.canonicalUrl, + }, + robots: "noindex, nofollow", + }; +} + +const PreviewPage = async ({ params }: Props) => { + console.log("xxx", { params }); + const { id } = params; + + const post = await getPostPreview({ id }); + + if (!post) { + notFound(); + } + + const ast = Markdoc.parse(post.body); + const content = Markdoc.transform(ast, config); + + return ( + <> +
+
+

{post.title}

+ {Markdoc.renderers.react(content, React, { + components: markdocComponents, + })} +
+ {post.tags.length > 0 && ( +
+ {post.tags.map(({ tag }) => ( + + {getCamelCaseFromLower(tag.title)} + + ))} +
+ )} +
+
+ +
+ + ); +}; + +export default PreviewPage; diff --git a/app/robots.ts b/app/robots.ts index 9a4f697d4..5911a1e86 100644 --- a/app/robots.ts +++ b/app/robots.ts @@ -9,6 +9,7 @@ export default function robots(): MetadataRoute.Robots { disallow: [ "/alpha/", "/api/", + "/draft/", "/settings/", "/metrics/", "/notifications/", diff --git a/server/lib/posts.ts b/server/lib/posts.ts index 7055bf789..dac67d25b 100644 --- a/server/lib/posts.ts +++ b/server/lib/posts.ts @@ -10,11 +10,16 @@ export const GetPostSchema = z.object({ slug: z.string(), }); +export const GetPreviewSchema = z.object({ + id: z.string(), +}); + export const GetTrendingSchema = z.object({ currentUserId: z.string().optional(), }); type GetPost = z.infer; +type GetPreview = z.infer; type GetTrending = z.infer; export async function getPost({ slug }: GetPost) { @@ -36,7 +41,6 @@ export async function getPost({ slug }: GetPost) { }, where: (posts, { eq }) => eq(posts.slug, slug), with: { - bookmarks: { columns: { userId: true } }, tags: { columns: { id: true }, with: { tag: { columns: { title: true } } }, @@ -53,12 +57,58 @@ export async function getPost({ slug }: GetPost) { }, }); - if (!response || response.published === null) { + if (!response) { + return null; + } + return response; + } catch (error) { + Sentry.captureException(error); + throw new Error("Error fetching post"); + } +} + +export async function getPostPreview({ id }: GetPreview) { + try { + GetPreviewSchema.parse({ id }); + + console.log("ALSDJHKASJHD ASKH"); + + const response = await db.query.post.findFirst({ + columns: { + id: true, + title: true, + body: true, + published: true, + updatedAt: true, + readTimeMins: true, + slug: true, + excerpt: true, + canonicalUrl: true, + showComments: true, + }, + where: (posts, { eq }) => eq(posts.id, id), + with: { + tags: { + columns: { id: true }, + with: { tag: { columns: { title: true } } }, + }, + user: { + columns: { + name: true, + image: true, + username: true, + bio: true, + id: true, + }, + }, + }, + }); + + if (!response) { return null; } - const currentUserBookmarkedPost = !!response.bookmarks.length; - response.bookmarks = []; - return { ...response, currentUserBookmarkedPost }; + + return response; } catch (error) { Sentry.captureException(error); throw new Error("Error fetching post"); From 2a5896faf6b6ba690dfbb2761092cc569fbfe36e Mon Sep 17 00:00:00 2001 From: NiallJoeMaher Date: Sat, 8 Jun 2024 12:26:07 +0100 Subject: [PATCH 2/2] Remove console.log statements and add option to get draft link in create post --- app/(app)/create/[[...paramsArr]]/_client.tsx | 394 ++++++++++-------- app/(app)/draft/[id]/page.tsx | 3 +- server/lib/posts.ts | 2 - styles/globals.css | 4 + 4 files changed, 221 insertions(+), 182 deletions(-) diff --git a/app/(app)/create/[[...paramsArr]]/_client.tsx b/app/(app)/create/[[...paramsArr]]/_client.tsx index eefef459f..5b6471796 100644 --- a/app/(app)/create/[[...paramsArr]]/_client.tsx +++ b/app/(app)/create/[[...paramsArr]]/_client.tsx @@ -20,7 +20,7 @@ import { config } from "@/markdoc/config"; import { useParams, useRouter } from "next/navigation"; import { usePrompt } from "@/components/PromptService"; import { Switch } from "@/components/Switch/Switch"; - +import copy from "copy-to-clipboard"; import { PostStatus, getPostStatus, isValidScheduleTime } from "@/utils/post"; const Create = () => { @@ -39,6 +39,7 @@ const Create = () => { const [isPostScheduled, setIsPostScheduled] = useState(false); const [shouldRefetch, setShouldRefetch] = useState(true); const [unsavedChanges, setUnsavedChanges] = useState(false); + const [copied, setCopied] = useState(false); const { unsavedChanges: _unsaved, setUnsavedChanges: _setUnsaved } = usePrompt(); @@ -112,6 +113,13 @@ const Create = () => { }, ); + const PREVIEW_URL = `${process.env.VERCEL_URL || "http://localhost:3000"}/draft/${postId}`; + + const handleCopyToClipboard = () => { + copy(PREVIEW_URL); + setCopied(true); + }; + useEffect(() => { if (isError) { toast.error("Error saving"); @@ -134,6 +142,11 @@ const Create = () => { } }, [dataStatus, shouldRefetch]); + useEffect(() => { + const to = setTimeout(setCopied, 2000, false); + return () => clearTimeout(to); + }, [copied]); + const getFormData = () => { const data = getValues(); const formData = { @@ -304,193 +317,218 @@ const Create = () => { <>
-
- -
-
-
-
- -