-
Couldn't load subscription status.
- Fork 841
fix(render): decode HTML entities in style attributes #2552
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
base: canary
Are you sure you want to change the base?
Changes from 1 commit
2fbd1d3
c82c45c
a991373
08b225e
9c37138
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 |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| '@react-email/render': patch | ||
| --- | ||
|
|
||
| Fix HTML entity encoding in style attributes | ||
|
|
||
| Fixes #1767 - Decodes HTML entities in style attributes to fix font-family declarations with quoted font names. Only decodes ampersands in href attributes to preserve HTML structure and avoid breaking attribute syntax. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| import { Suspense } from 'react'; | ||
| import type { Options } from '../shared/options'; | ||
| import { decodeAttributeEntities } from '../shared/utils/decode-html-entities'; | ||
| import { pretty } from '../shared/utils/pretty'; | ||
| import { toPlainText } from '../shared/utils/to-plain-text'; | ||
| import { readStream } from './read-stream'; | ||
|
|
@@ -43,7 +44,8 @@ export const render = async (node: React.ReactNode, options?: Options) => { | |
| const doctype = | ||
| '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'; | ||
|
|
||
| const document = `${doctype}${html.replace(/<!DOCTYPE.*?>/, '')}`; | ||
| const decodedHtml = decodeAttributeEntities(html); | ||
|
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. Decoding the rendered HTML here replaces Prompt for AI agents |
||
| const document = `${doctype}${decodedHtml.replace(/<!DOCTYPE.*?>/, '')}`; | ||
|
|
||
| if (options?.pretty) { | ||
| return pretty(document); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| /** | ||
| * Decodes HTML entities in href and style attributes | ||
| * This is necessary because React's rendering encodes characters like ampersands | ||
| * and quotes in attribute values, which can break: | ||
| * - Links with query parameters (e.g., ?param1=value1¶m2=value2) | ||
| * - CSS font-family declarations with quotes | ||
| * | ||
| * Note: We only decode safe entities and avoid decoding < and > to prevent | ||
| * breaking HTML structure. Quotes are only decoded in style attributes to avoid | ||
| * breaking href attribute syntax. | ||
| */ | ||
| export const decodeAttributeEntities = (html: string): string => { | ||
| const decodeHrefValue = (value: string): string => { | ||
| // Only decode ampersands in hrefs to fix URL query parameters | ||
| // Do NOT decode quotes to avoid breaking the attribute syntax | ||
| return value.replace(/&/g, '&'); | ||
|
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. can you elaborate on why you didn't use 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. Using |
||
| }; | ||
|
|
||
| const decodeStyleValue = (value: string): string => { | ||
| // Decode quotes and ampersands in style attributes | ||
| // This is safe because CSS can contain quoted strings (e.g., font-family) | ||
| return value | ||
iagocavalcante marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| .replace(/&/g, '&') | ||
| .replace(/"/g, '"') | ||
| .replace(/'/g, "'") | ||
| .replace(/'/g, "'"); | ||
| }; | ||
|
|
||
| return html | ||
| .replace(/href="([^"]*)"/g, (_match, hrefContent) => { | ||
| return `href="${decodeHrefValue(hrefContent)}"`; | ||
| }) | ||
| .replace(/style="([^"]*)"/g, (_match, styleContent) => { | ||
| return `style="${decodeStyleValue(styleContent)}"`; | ||
| }); | ||
| }; | ||
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.
can we stick with just this initial portion here, and can you make it all lowercase?