fix: Properly decode HTML entities in chat messages
Some checks failed
Dev Deployment (Zero Downtime) / deploy-dev (push) Has been cancelled
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:
@@ -51,6 +51,14 @@ export default function ChatWidget() {
|
|||||||
}
|
}
|
||||||
}, [isOpen]);
|
}, [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
|
// Load messages from localStorage
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
@@ -61,6 +69,7 @@ export default function ChatWidget() {
|
|||||||
setMessages(
|
setMessages(
|
||||||
parsed.map((m: Message) => ({
|
parsed.map((m: Message) => ({
|
||||||
...m,
|
...m,
|
||||||
|
text: decodeHtmlEntities(m.text), // Decode HTML entities when loading
|
||||||
timestamp: new Date(m.timestamp),
|
timestamp: new Date(m.timestamp),
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
@@ -72,7 +81,7 @@ export default function ChatWidget() {
|
|||||||
setMessages([
|
setMessages([
|
||||||
{
|
{
|
||||||
id: "welcome",
|
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",
|
sender: "bot",
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
},
|
},
|
||||||
@@ -128,12 +137,8 @@ export default function ChatWidget() {
|
|||||||
// Decode HTML entities in the reply
|
// Decode HTML entities in the reply
|
||||||
let replyText = data.reply || "Sorry, I couldn't process that. Please try again.";
|
let replyText = data.reply || "Sorry, I couldn't process that. Please try again.";
|
||||||
|
|
||||||
// Decode HTML entities client-side as well (double safety)
|
// Decode HTML entities client-side (double safety)
|
||||||
if (typeof window !== 'undefined') {
|
replyText = decodeHtmlEntities(replyText);
|
||||||
const textarea = document.createElement('textarea');
|
|
||||||
textarea.innerHTML = replyText;
|
|
||||||
replyText = textarea.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const botMessage: Message = {
|
const botMessage: Message = {
|
||||||
id: (Date.now() + 1).toString(),
|
id: (Date.now() + 1).toString(),
|
||||||
@@ -237,7 +242,7 @@ export default function ChatWidget() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-bold text-sm">
|
<h3 className="font-bold text-sm">
|
||||||
Dennis's AI Assistant
|
Dennis{'\''}s AI Assistant
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs text-white/80">Always online</p>
|
<p className="text-xs text-white/80">Always online</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -370,7 +375,7 @@ export default function ChatWidget() {
|
|||||||
{/* Quick Actions */}
|
{/* Quick Actions */}
|
||||||
<div className="flex gap-2 mt-2 overflow-x-auto pb-1 scrollbar-hide">
|
<div className="flex gap-2 mt-2 overflow-x-auto pb-1 scrollbar-hide">
|
||||||
{[
|
{[
|
||||||
"What are Dennis's skills?",
|
"What are Dennis's skills?",
|
||||||
"Tell me about his projects",
|
"Tell me about his projects",
|
||||||
"How can I contact him?",
|
"How can I contact him?",
|
||||||
].map((suggestion, index) => (
|
].map((suggestion, index) => (
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export function decodeHtmlEntitiesServer(text: string): string {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map of common HTML entities
|
// Map of common HTML entities (including all variations of apostrophe)
|
||||||
const entityMap: Record<string, string> = {
|
const entityMap: Record<string, string> = {
|
||||||
''': "'",
|
''': "'",
|
||||||
'"': '"',
|
'"': '"',
|
||||||
@@ -33,9 +33,26 @@ export function decodeHtmlEntitiesServer(text: string): string {
|
|||||||
'/': '/',
|
'/': '/',
|
||||||
'`': '`',
|
'`': '`',
|
||||||
'=': '=',
|
'=': '=',
|
||||||
|
'’': "'",
|
||||||
|
'‘': "'",
|
||||||
|
'”': '"',
|
||||||
|
'“': '"',
|
||||||
};
|
};
|
||||||
|
|
||||||
return text.replace(/&[#\w]+;/g, (entity) => {
|
// First replace known entities
|
||||||
return entityMap[entity] || entity;
|
let decoded = text;
|
||||||
|
for (const [entity, replacement] of Object.entries(entityMap)) {
|
||||||
|
decoded = decoded.replace(new RegExp(entity, 'gi'), replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then handle numeric entities (' ' 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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user