fix: Properly decode HTML entities in chat messages
Some checks failed
Dev Deployment (Zero Downtime) / deploy-dev (push) Has been cancelled

- Fix ' not being decoded to apostrophe
- Decode HTML entities when loading messages from localStorage
- Improve server-side HTML entity decoding to handle all variations
- Replace hardcoded ' in static text with regular apostrophes
- Add support for more HTML entity variations (rsquo, lsquo, etc.)
This commit is contained in:
2026-01-09 18:07:32 +01:00
parent 9c24fdf5bd
commit 42a586d183
2 changed files with 34 additions and 12 deletions

View File

@@ -51,6 +51,14 @@ export default function ChatWidget() {
}
}, [isOpen]);
// Helper function to decode HTML entities
const decodeHtmlEntities = (text: string): string => {
if (!text || typeof text !== 'string') return text;
const textarea = document.createElement('textarea');
textarea.innerHTML = text;
return textarea.value;
};
// Load messages from localStorage
useEffect(() => {
if (typeof window !== "undefined") {
@@ -61,6 +69,7 @@ export default function ChatWidget() {
setMessages(
parsed.map((m: Message) => ({
...m,
text: decodeHtmlEntities(m.text), // Decode HTML entities when loading
timestamp: new Date(m.timestamp),
})),
);
@@ -72,7 +81,7 @@ export default function ChatWidget() {
setMessages([
{
id: "welcome",
text: "Hi! I'm Dennis's AI assistant. Ask me anything about his skills, projects, or experience! 🚀",
text: "Hi! I'm Dennis's AI assistant. Ask me anything about his skills, projects, or experience! 🚀",
sender: "bot",
timestamp: new Date(),
},
@@ -128,12 +137,8 @@ export default function ChatWidget() {
// Decode HTML entities in the reply
let replyText = data.reply || "Sorry, I couldn't process that. Please try again.";
// Decode HTML entities client-side as well (double safety)
if (typeof window !== 'undefined') {
const textarea = document.createElement('textarea');
textarea.innerHTML = replyText;
replyText = textarea.value;
}
// Decode HTML entities client-side (double safety)
replyText = decodeHtmlEntities(replyText);
const botMessage: Message = {
id: (Date.now() + 1).toString(),
@@ -237,7 +242,7 @@ export default function ChatWidget() {
</div>
<div>
<h3 className="font-bold text-sm">
Dennis&apos;s AI Assistant
Dennis{'\''}s AI Assistant
</h3>
<p className="text-xs text-white/80">Always online</p>
</div>
@@ -370,7 +375,7 @@ export default function ChatWidget() {
{/* Quick Actions */}
<div className="flex gap-2 mt-2 overflow-x-auto pb-1 scrollbar-hide">
{[
"What are Dennis&apos;s skills?",
"What are Dennis's skills?",
"Tell me about his projects",
"How can I contact him?",
].map((suggestion, index) => (

View File

@@ -21,7 +21,7 @@ export function decodeHtmlEntitiesServer(text: string): string {
return text;
}
// Map of common HTML entities
// Map of common HTML entities (including all variations of apostrophe)
const entityMap: Record<string, string> = {
'&apos;': "'",
'&quot;': '"',
@@ -33,9 +33,26 @@ export function decodeHtmlEntitiesServer(text: string): string {
'&#x2F;': '/',
'&#x60;': '`',
'&#x3D;': '=',
'&rsquo;': "'",
'&lsquo;': "'",
'&rdquo;': '"',
'&ldquo;': '"',
};
return text.replace(/&[#\w]+;/g, (entity) => {
return entityMap[entity] || entity;
});
// First replace known entities
let decoded = text;
for (const [entity, replacement] of Object.entries(entityMap)) {
decoded = decoded.replace(new RegExp(entity, 'gi'), replacement);
}
// Then handle numeric entities (&#39; &#x27; etc.)
decoded = decoded.replace(/&#(\d+);/g, (match, num) => {
return String.fromCharCode(parseInt(num, 10));
});
decoded = decoded.replace(/&#x([0-9a-f]+);/gi, (match, hex) => {
return String.fromCharCode(parseInt(hex, 16));
});
return decoded;
}