Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
05f7c960
Commit
05f7c960
authored
Nov 26, 2025
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: add HTML sanitization and dynamic theme loading
parent
363bc9f4
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
118 additions
and
10 deletions
+118
-10
CodeBlock.tsx
web/src/components/MemoContent/CodeBlock.tsx
+118
-10
No files found.
web/src/components/MemoContent/CodeBlock.tsx
View file @
05f7c960
import
DOMPurify
from
"dompurify"
;
import
hljs
from
"highlight.js"
;
import
{
CheckIcon
,
CopyIcon
}
from
"lucide-react"
;
import
{
CheckIcon
,
CopyIcon
}
from
"lucide-react"
;
import
{
useState
}
from
"react"
;
import
{
observer
}
from
"mobx-react-lite"
;
import
{
useEffect
,
useMemo
,
useState
}
from
"react"
;
import
{
cn
}
from
"@/lib/utils"
;
import
{
cn
}
from
"@/lib/utils"
;
import
{
instanceStore
}
from
"@/store"
;
import
{
MermaidBlock
}
from
"./MermaidBlock"
;
import
{
MermaidBlock
}
from
"./MermaidBlock"
;
interface
PreProps
{
interface
PreProps
{
...
@@ -8,7 +12,7 @@ interface PreProps {
...
@@ -8,7 +12,7 @@ interface PreProps {
className
?:
string
;
className
?:
string
;
}
}
export
const
CodeBlock
=
({
children
,
className
,
...
props
}:
PreProps
)
=>
{
export
const
CodeBlock
=
observer
(
({
children
,
className
,
...
props
}:
PreProps
)
=>
{
const
[
copied
,
setCopied
]
=
useState
(
false
);
const
[
copied
,
setCopied
]
=
useState
(
false
);
// Extract the code element and its props
// Extract the code element and its props
...
@@ -29,6 +33,115 @@ export const CodeBlock = ({ children, className, ...props }: PreProps) => {
...
@@ -29,6 +33,115 @@ export const CodeBlock = ({ children, className, ...props }: PreProps) => {
);
);
}
}
// If it's __html special language, render sanitized HTML
if
(
language
===
"__html"
)
{
const
sanitizedHTML
=
DOMPurify
.
sanitize
(
codeContent
,
{
ALLOWED_TAGS
:
[
"div"
,
"span"
,
"p"
,
"br"
,
"strong"
,
"b"
,
"em"
,
"i"
,
"u"
,
"s"
,
"strike"
,
"h1"
,
"h2"
,
"h3"
,
"h4"
,
"h5"
,
"h6"
,
"blockquote"
,
"code"
,
"pre"
,
"ul"
,
"ol"
,
"li"
,
"dl"
,
"dt"
,
"dd"
,
"table"
,
"thead"
,
"tbody"
,
"tr"
,
"th"
,
"td"
,
"a"
,
"img"
,
"figure"
,
"figcaption"
,
"hr"
,
"small"
,
"sup"
,
"sub"
,
],
ALLOWED_ATTR
:
"href title alt src width height class id style target rel colspan rowspan"
.
split
(
" "
),
FORBID_ATTR
:
"onerror onload onclick onmouseover onfocus onblur onchange"
.
split
(
" "
),
FORBID_TAGS
:
"script iframe object embed form input button"
.
split
(
" "
),
});
return
(
<
div
className=
"w-full overflow-auto my-2!"
dangerouslySetInnerHTML=
{
{
__html
:
sanitizedHTML
,
}
}
/>
);
}
const
appTheme
=
instanceStore
.
state
.
theme
;
const
isDarkTheme
=
appTheme
.
includes
(
"dark"
);
// Dynamically load highlight.js theme based on app theme
useEffect
(()
=>
{
const
dynamicImportStyle
=
async
()
=>
{
// Remove any existing highlight.js style
const
existingStyle
=
document
.
querySelector
(
"style[data-hljs-theme]"
);
if
(
existingStyle
)
{
existingStyle
.
remove
();
}
try
{
const
cssModule
=
isDarkTheme
?
await
import
(
"highlight.js/styles/github-dark-dimmed.css?inline"
)
:
await
import
(
"highlight.js/styles/github.css?inline"
);
// Create and inject the style
const
style
=
document
.
createElement
(
"style"
);
style
.
textContent
=
cssModule
.
default
;
style
.
setAttribute
(
"data-hljs-theme"
,
isDarkTheme
?
"dark"
:
"light"
);
document
.
head
.
appendChild
(
style
);
}
catch
(
error
)
{
console
.
warn
(
"Failed to load highlight.js theme:"
,
error
);
}
};
dynamicImportStyle
();
},
[
appTheme
,
isDarkTheme
]);
// Highlight code using highlight.js
const
highlightedCode
=
useMemo
(()
=>
{
try
{
const
lang
=
hljs
.
getLanguage
(
language
);
if
(
lang
)
{
return
hljs
.
highlight
(
codeContent
,
{
language
:
language
,
}).
value
;
}
}
catch
{
// Skip error and use default highlighted code.
}
// Escape any HTML entities when rendering original content.
return
Object
.
assign
(
document
.
createElement
(
"span"
),
{
textContent
:
codeContent
,
}).
innerHTML
;
},
[
language
,
codeContent
]);
const
handleCopy
=
async
()
=>
{
const
handleCopy
=
async
()
=>
{
try
{
try
{
await
navigator
.
clipboard
.
writeText
(
codeContent
);
await
navigator
.
clipboard
.
writeText
(
codeContent
);
...
@@ -45,12 +158,7 @@ export const CodeBlock = ({ children, className, ...props }: PreProps) => {
...
@@ -45,12 +158,7 @@ export const CodeBlock = ({ children, className, ...props }: PreProps) => {
<
span
className=
"text-[10px] font-medium text-muted-foreground/60 uppercase tracking-wider select-none"
>
{
language
}
</
span
>
<
span
className=
"text-[10px] font-medium text-muted-foreground/60 uppercase tracking-wider select-none"
>
{
language
}
</
span
>
<
button
<
button
onClick=
{
handleCopy
}
onClick=
{
handleCopy
}
className=
{
cn
(
className=
{
cn
(
"p-1.5 rounded-md transition-all"
,
"hover:bg-accent/50"
,
copied
?
"text-primary"
:
"text-muted-foreground"
)
}
"p-1.5 rounded-md transition-all"
,
"hover:bg-accent/50"
,
"focus:outline-none focus:ring-1 focus:ring-ring"
,
copied
?
"text-primary"
:
"text-muted-foreground"
,
)
}
aria
-
label=
{
copied
?
"Copied"
:
"Copy code"
}
aria
-
label=
{
copied
?
"Copied"
:
"Copy code"
}
title=
{
copied
?
"Copied!"
:
"Copy code"
}
title=
{
copied
?
"Copied!"
:
"Copy code"
}
>
>
...
@@ -58,8 +166,8 @@ export const CodeBlock = ({ children, className, ...props }: PreProps) => {
...
@@ -58,8 +166,8 @@ export const CodeBlock = ({ children, className, ...props }: PreProps) => {
</
button
>
</
button
>
</
div
>
</
div
>
<
div
className=
{
className
}
{
...
props
}
>
<
div
className=
{
className
}
{
...
props
}
>
{
children
}
<
code
className=
{
`language-${language}`
}
dangerouslySetInnerHTML=
{
{
__html
:
highlightedCode
}
}
/>
</
div
>
</
div
>
</
pre
>
</
pre
>
);
);
};
}
)
;
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment