Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 167
feat: Ensure the article appears exactly as it will when published #1102#1153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
a059d85560a7f742cd56d402bfd2417cb27753bc7f37a91d2917d78e516e714b174cf9e6dc4c5c548efd2fc73870a49461File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,4 +1,5 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import React from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { RenderableTreeNode } from "@markdoc/markdoc"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Markdoc from "@markdoc/markdoc"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Link from "next/link"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import BioBar from "@/components/BioBar/BioBar"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @@ -13,6 +14,10 @@ import ArticleAdminPanel from "@/components/ArticleAdminPanel/ArticleAdminPanel" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { type Metadata } from "next"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getPost } from "@/server/lib/posts"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getCamelCaseFromLower } from "@/utils/utils"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { generateHTML } from "@tiptap/html"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { TiptapExtensions } from "@/components/editor/editor/extensions"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import DOMPurify from "isomorphic-dompurify"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { JSONContent } from "@tiptap/core"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type Props = { params: { slug: string } }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @@ -57,6 +62,20 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const parseJSON = (str: string): JSONContent | null => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return JSON.parse(str); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const renderSanitizedTiptapContent = (jsonContent: JSONContent) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const rawHtml = generateHTML(jsonContent, [...TiptapExtensions]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Sanitize the HTML | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return DOMPurify.sanitize(rawHtml); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ArticlePage = async ({ params }: Props) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const session = await getServerAuthSession(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { slug } = params; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @@ -66,11 +85,24 @@ const ArticlePage = async ({ params }: Props) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const post = await getPost({ slug }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!post) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| notFound(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return notFound(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ast = Markdoc.parse(post.body); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const content = Markdoc.transform(ast, config); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const parsedBody = parseJSON(post.body); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isTiptapContent = parsedBody?.type === "doc"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let renderedContent: string | RenderableTreeNode; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
dineshsutihar marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isTiptapContent && parsedBody) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const jsonContent = parsedBody; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| renderedContent = renderSanitizedTiptapContent(jsonContent); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ast = Markdoc.parse(post.body); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const transformedContent = Markdoc.transform(ast, config); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| renderedContent = Markdoc.renderers.react(transformedContent, React, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| components: markdocComponents, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) as unknown as string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+91
to
+104
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constparsedBody=parseJSON(post.body); | |
| constisTiptapContent=parsedBody?.type==="doc"; | |
| letrenderedContent: string|RenderableTreeNode; | |
| if(isTiptapContent&&parsedBody){ | |
| constjsonContent=parsedBody; | |
| renderedContent=renderSanitizedTiptapContent(jsonContent); | |
| }else{ | |
| constast=Markdoc.parse(post.body); | |
| consttransformedContent=Markdoc.transform(ast,config); | |
| renderedContent=Markdoc.renderers.react(transformedContent,React,{ | |
| components: markdocComponents, | |
| })as unknown asstring; | |
| constparsedBody=parseJSON(post.body); | |
| constisTiptapContent=parsedBody?.type==="doc"; | |
| letrenderedContent: string|RenderableTreeNode; | |
| if(isTiptapContent&&parsedBody){ | |
| constjsonContent=parsedBody; | |
| renderedContent=renderSanitizedTiptapContent(jsonContent); | |
| }else{ | |
| constast=Markdoc.parse(post.body); | |
| consttransformedContent=Markdoc.transform(ast,config); | |
| renderedContent=Markdoc.renderers.react(transformedContent,React,{ | |
| components: markdocComponents, | |
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep the type assertion because renderedContent needs to be treated as a string for use in dangerouslySetInnerHTML, and removing it could lead to runtime issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dineshsutihar, understood. Thanks for the clarification. Keeping the type assertion ensures correct behavior when using dangerouslySetInnerHTML.
🐰🤔
✏️ Learnings added
Learnt from: dineshsutihar
PR: codu-code/codu#1153
File: app/(app)/articles/[slug]/page.tsx:91-104
Timestamp: 2024-10-22T08:43:13.045Z
Learning: In `app/(app)/articles/[slug]/page.tsx`, when rendering content, the `renderedContent` needs to be treated as a string using a type assertion because it's used with `dangerouslySetInnerHTML`, and removing the type assertion could lead to runtime issues.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider memoizing the TiptapExtensions array
The
renderSanitizedTiptapContentfunction creates a new array spread of TiptapExtensions on every call, which could impact performance for large articles.📝 Committable suggestion