CustomEmojiTextRenderer.svelte
1 <script lang="ts"> 2 /** 3 * @component CustomEmojiTextRenderer 4 * Renders text (plain or HTML) and replaces Mastodon emoji shortcodes with image tags. 5 */ 6 interface Props { 7 /** The text or HTML to render. */ 8 text: string; 9 /** Array of emoji objects from the Mastodon API. */ 10 emojis: { 11 shortcode: string; 12 url: string; 13 [key: string]: unknown; 14 }[]; 15 } 16 17 let { text, emojis }: Props = $props(); 18 19 // Create a lookup map for faster replacement 20 const emojiMap = $derived.by(() => { 21 const map: Record<string, string> = {}; 22 if (emojis) { 23 for (const emoji of emojis) { 24 map[emoji.shortcode] = emoji.url; 25 } 26 } 27 return map; 28 }); 29 30 // Replace :shortcode: with <img /> 31 const renderedText = $derived.by(() => { 32 if (!text) return ''; 33 if (!emojis || emojis.length === 0) return text; 34 35 return text.replace(/:(\w+):/g, (match, shortcode) => { 36 const url = emojiMap[shortcode]; 37 if (url) { 38 return `<img class="fk-custom-emoji-text-renderer__emoji" src="${url}" alt=":${shortcode}:" title=":${shortcode}:" width="20" height="20" />`; 39 } 40 return match; 41 }); 42 }); 43 </script> 44 45 <span class="fk-custom-emoji-text-renderer"> 46 {@html renderedText} 47 </span> 48 49 <style> 50 .fk-custom-emoji-text-renderer { 51 display: inline; 52 word-break: break-word; 53 color: inherit; 54 } 55 56 :global(.fk-custom-emoji-text-renderer__emoji) { 57 height: 20px; 58 width: 20px; 59 vertical-align: middle; 60 object-fit: contain; 61 display: inline-block; 62 margin: -0.2em 0.15em 0; 63 } 64 </style>