@@ -5,26 +5,64 @@ import { sheetModules } from "@/data/sheet";
55import Link from "next/link" ;
66import { ArrowLeft , Download , Share2 , Check } from "lucide-react" ;
77import { Badge } from "@/components/ui/badge" ;
8- import { useState } from "react" ;
8+ import { useState , useMemo } from "react" ;
9+
10+ // Helper function to sanitize HTML - only works on client side
11+ const sanitizeHTMLSync = (
12+ html : string ,
13+ options ?: { ALLOWED_TAGS : string [ ] }
14+ ) : string => {
15+ if ( typeof window === "undefined" ) {
16+ return html ;
17+ }
18+
19+ // Lazy load DOMPurify only when needed on client side
20+ // Using require to avoid SSR issues
21+ try {
22+ const DOMPurify = require ( "dompurify" ) ;
23+ // Handle both default export and named export
24+ const purify = DOMPurify . default || DOMPurify ;
25+ if ( options ) {
26+ return purify . sanitize ( html , options ) ;
27+ }
28+ return purify . sanitize ( html ) ;
29+ } catch ( error ) {
30+ // Fallback if DOMPurify fails to load
31+ console . warn (
32+ "DOMPurify failed to load, returning unsanitized HTML:" ,
33+ error
34+ ) ;
35+ return html ;
36+ }
37+ } ;
938
1039export default function ModuleDocPage ( ) {
1140 const params = useParams ( ) ;
1241 const moduleId = params ?. moduleId as string ;
1342 const [ copied , setCopied ] = useState ( false ) ;
14-
43+
1544 const currentModule = sheetModules . find ( ( m ) => m . id === moduleId ) ;
1645
46+ const sanitizedDocContent = useMemo ( ( ) => {
47+ if ( ! currentModule ?. docContent ) return "" ;
48+ return sanitizeHTMLSync ( currentModule . docContent ) ;
49+ } , [ currentModule ?. docContent ] ) ;
50+
1751 const handleDownloadPDF = ( ) => {
1852 if ( ! currentModule ) return ;
19-
53+
2054 const printWindow = window . open ( "" , "_blank" ) ;
2155 if ( ! printWindow ) return ;
2256
57+ const sanitizedModuleName = sanitizeHTMLSync ( currentModule . name , {
58+ ALLOWED_TAGS : [ ] ,
59+ } ) ;
60+
2361 const htmlContent = `
2462 <!DOCTYPE html>
2563 <html>
2664 <head>
27- <title>${ currentModule . name } - 30 days of Open Source sheet</title>
65+ <title>${ sanitizedModuleName } - 30 days of Open Source sheet</title>
2866 <style>
2967 body {
3068 font-family: 'Courier New', monospace;
@@ -57,9 +95,9 @@ export default function ModuleDocPage() {
5795 </style>
5896 </head>
5997 <body>
60- <h1>${ currentModule . name } </h1>
98+ <h1>${ sanitizedModuleName } </h1>
6199 <div class="content">
62- ${ currentModule . docContent }
100+ ${ sanitizedDocContent }
63101 </div>
64102 </body>
65103 </html>
@@ -100,7 +138,9 @@ export default function ModuleDocPage() {
100138 < ArrowLeft className = "h-4 w-4" />
101139 < span > Back to Sheet</ span >
102140 </ Link >
103- < h1 className = "text-3xl font-bold text-white mb-2" > { currentModule . name } </ h1 >
141+ < h1 className = "text-3xl font-bold text-white mb-2" >
142+ { currentModule . name }
143+ </ h1 >
104144 </ div >
105145
106146 < div className = "bg-ox-content rounded-lg p-8 border border-ox-header text-center" >
@@ -154,13 +194,16 @@ export default function ModuleDocPage() {
154194 </ button >
155195 </ div >
156196 </ div >
157- < h1 className = "text-3xl font-bold text-white mb-2" > { currentModule . name } </ h1 >
197+ < h1 className = "text-3xl font-bold text-white mb-2" >
198+ { currentModule . name }
199+ </ h1 >
158200 </ div >
159201
160202 { /* Content */ }
161203 < div className = "bg-ox-content rounded-lg p-8 prose prose-invert max-w-none font-DMfont border border-ox-header" >
162204 < div
163- dangerouslySetInnerHTML = { { __html : currentModule . docContent } }
205+ // eslint-disable-next-line react/no-danger -- Safe: docContent is sanitized with DOMPurify before rendering
206+ dangerouslySetInnerHTML = { { __html : sanitizedDocContent } }
164207 className = "text-white [&_h1]:text-2xl [&_h1]:font-bold [&_h1]:mb-4 [&_h1]:mt-6 [&_h1]:text-white [&_h2]:text-xl [&_h2]:font-semibold [&_h2]:mb-3 [&_h2]:mt-5 [&_h2]:text-white [&_p]:text-gray-300 [&_p]:mb-4 [&_p]:leading-relaxed [&_ul]:list-disc [&_ul]:ml-6 [&_ul]:mb-4 [&_ul]:text-gray-300 [&_li]:mb-2 [&_pre]:bg-ox-sidebar [&_pre]:p-4 [&_pre]:rounded [&_pre]:overflow-x-auto [&_pre]:mb-4 [&_pre]:font-DMfont [&_pre]:border [&_pre]:border-ox-header [&_code]:text-ox-purple [&_code]:bg-ox-sidebar [&_code]:px-1 [&_code]:py-0.5 [&_code]:rounded [&_code]:font-DMfont [&_img]:max-w-full [&_img]:h-auto [&_img]:rounded-lg [&_img]:my-5 [&_img]:border [&_img]:border-ox-header"
165208 />
166209 </ div >
0 commit comments