feat: add emoticon conversion feature for text and HTML inputs

This commit is contained in:
2026-02-21 23:55:15 +11:00
parent f00e20b1ac
commit e218ab5c37
5 changed files with 200 additions and 12 deletions

View File

@@ -0,0 +1,164 @@
/**
* Text emoticon to emoji converter
* Automatically converts common text emoticons like :) :D etc. to their emoji equivalents
*/
// Map of text emoticons to emoji
// Using common patterns that don't interfere with markdown links or other syntax
const EMOTICON_MAP: Record<string, string> = {
// Happy faces
':D': '😀',
':-D': '😀',
':)': '🙂',
':-)': '🙂',
'=)': '😊',
':]': '😊',
':>': '😊',
// Sad faces
':(': '☹️',
':-(': '☹️',
'=[': '☹️',
':<': '☹️',
// Other emotions
';)': '😉',
';-)': '😉',
':P': '😛',
':-P': '😛',
':p': '😛',
':-p': '😛',
'xD': '😆',
'XD': '😆',
':|': '😐',
':-|': '😐',
':O': '😮',
':-O': '😮',
':o': '😮',
':-o': '😮',
'O:)': '😇',
'O:-)': '😇',
':*': '😘',
':-*': '😘',
'<3': '❤️',
'</3': '💔',
// Surprised/Shocked
':0': '😯',
':-0': '😯',
// Cool
'8)': '😎',
'8-)': '😎',
'B)': '😎',
'B-)': '😎',
// Crying
':\'(': '😢',
':\')': '😂',
// Neutral
':/': '😕',
':-/': '😕',
':\\': '😕',
':-\\': '😕',
};
/**
* Converts text emoticons in a string to their emoji equivalents
* Use backslash to escape: \:) will remain as :) without converting
* @param text The text to process
* @returns The text with emoticons converted to emoji
*/
export function convertEmoticons(text: string): string {
let result = text;
// Sort emoticons by length (longest first) to handle overlapping patterns
// e.g., :-) before :)
const emoticons = Object.keys(EMOTICON_MAP).sort((a, b) => b.length - a.length);
for (const emoticon of emoticons) {
// Escape special regex characters
const escapedEmoticon = emoticon.replace(/[.*+?^${}()|[\]\\<>]/g, '\\$&');
// Match emoticons with optional backslash escape
// Matches either \:) (escaped) or :) (not escaped, and not preceded by backslash)
const regex = new RegExp(
`(^|\\s)(\\\\)?(${escapedEmoticon})(?=\\s|$)`,
'g'
);
result = result.replace(regex, (match, prefix, backslash, emote) => {
// If backslash is present, remove it but keep the emoticon literal
if (backslash) {
return prefix + emote;
}
// Otherwise convert to emoji
return prefix + EMOTICON_MAP[emoticon];
});
}
return result;
}
/**
* Converts text emoticons in HTML to their emoji equivalents
* Handles HTML entities like &lt; and &gt;
* Use backslash to escape: \:) will remain as :) without converting
* @param html The HTML text to process
* @returns The HTML with emoticons converted to emoji
*/
export function convertEmoticonsInHtml(html: string): string {
let result = html;
// Sort emoticons by length (longest first) to handle overlapping patterns
const emoticons = Object.keys(EMOTICON_MAP).sort((a, b) => b.length - a.length);
for (const emoticon of emoticons) {
// Convert emoticon to HTML entity version (for < and >)
const htmlEmoticon = emoticon
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
// Escape special regex characters
const escapedEmoticon = htmlEmoticon.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// Match emoticons at start of string, after whitespace, or after HTML tags
// With optional backslash escape
const regex = new RegExp(
`(^|\\s|>)(\\\\)?(${escapedEmoticon})(?=\\s|<|$)`,
'g'
);
result = result.replace(regex, (match, prefix, backslash, emote) => {
// If backslash is present, remove it but keep the emoticon literal
if (backslash) {
return prefix + emote;
}
// Otherwise convert to emoji
return prefix + EMOTICON_MAP[emoticon];
});
}
return result;
}
/**
* Checks if a string contains any text emoticons
* @param text The text to check
* @returns True if the text contains emoticons
*/
export function containsEmoticons(text: string): boolean {
const emoticons = Object.keys(EMOTICON_MAP);
for (const emoticon of emoticons) {
const escapedEmoticon = emoticon.replace(/[.*+?^${}()|[\]\\<>]/g, '\\$&');
const regex = new RegExp(`(^|\\s)(${escapedEmoticon})(?=\\s|$)`);
if (regex.test(text)) {
return true;
}
}
return false;
}