-
-
Notifications
You must be signed in to change notification settings - Fork 146
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
feat: Ensure the article appears exactly as it will when published #1102 #1153
Changes from all commits
a059d85
560a7f7
42cd56d
402bfd2
417cb27
753bc7f
37a91d2
917d78e
516e714
b174cf9
e6dc4c5
c548efd
2fc7387
0a49461
File filter
Filter by extension
Conversations
Jump to
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.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LGTM: Improved content type detection and rendering The new logic effectively detects and renders both Tiptap and Markdoc content, aligning with the PR objectives. Remove the unnecessary type assertion in the Markdoc rendering path: - renderedContent = Markdoc.renderers.react(transformedContent, React, {
- components: markdocComponents,
- }) as unknown as string;
+ renderedContent = Markdoc.renderers.react(transformedContent, React, {
+ components: markdocComponents,
+ }); This change eliminates the incorrect casting of a React element to string, improving type safety. 📝 Committable suggestion
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep the type assertion because There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 🐰🤔 ✏️ Learnings added
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -83,10 +115,16 @@ const ArticlePage = async ({ params }: Props) => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div className="mx-auto break-words px-2 pb-4 sm:px-4 md:max-w-3xl"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<article className="prose mx-auto max-w-3xl dark:prose-invert lg:prose-lg"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<h1>{post.title}</h1> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{Markdoc.renderers.react(content, React, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
components: markdocComponents, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
})} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{!isTiptapContent && <h1>{post.title}</h1>} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{isTiptapContent ? ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
dangerouslySetInnerHTML={{ __html: renderedContent }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
dineshsutihar marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
className="tiptap-content" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) : ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div>{renderedContent}</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</article> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{post.tags.length > 0 && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<section className="flex flex-wrap gap-3"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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
renderSanitizedTiptapContent
function creates a new array spread of TiptapExtensions on every call, which could impact performance for large articles.📝 Committable suggestion