/ src / lib / components / CustomEmojiTextRenderer.svelte
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>