slack status without the slack status.zzstoatzz.io/
quickslice

Fix emoji loading inefficiency with intelligent resolver

- Add emoji-resolver.js that loads filename mappings from API
- Replace hardcoded .png/.gif extensions with placeholder images
- Eliminate 404 errors by using correct extensions from the start
- Support any file extension (.png, .gif, .jpg, .webp, etc.)
- Maintain backward compatibility for existing data

The resolver loads once, caches the mappings, and updates all emoji
images with their correct filenames, preventing unnecessary 404s.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+133 -9
static
templates
+117
static/emoji-resolver.js
··· 1 + // Emoji Resolver Module - Handles mapping emoji names to correct filenames 2 + (function() { 3 + 'use strict'; 4 + 5 + // Cache for emoji name -> filename mapping 6 + let emojiMap = null; 7 + let loadPromise = null; 8 + 9 + // Load emoji mapping from API 10 + async function loadEmojiMap() { 11 + if (emojiMap) return emojiMap; 12 + if (loadPromise) return loadPromise; 13 + 14 + loadPromise = fetch('/api/custom-emojis') 15 + .then(response => response.json()) 16 + .then(data => { 17 + if (!Array.isArray(data)) { 18 + console.error('Invalid emoji data received'); 19 + return new Map(); 20 + } 21 + emojiMap = new Map(data.map(emoji => [emoji.name, emoji.filename])); 22 + return emojiMap; 23 + }) 24 + .catch(err => { 25 + console.error('Failed to load emoji map:', err); 26 + emojiMap = new Map(); 27 + return emojiMap; 28 + }); 29 + 30 + return loadPromise; 31 + } 32 + 33 + // Get the correct emoji filename for a given name 34 + function getEmojiFilename(emojiName) { 35 + if (!emojiMap) return null; 36 + return emojiMap.get(emojiName); 37 + } 38 + 39 + // Update a single emoji image element 40 + function updateEmojiImage(img) { 41 + const emojiName = img.getAttribute('data-emoji-name'); 42 + if (!emojiName) return; 43 + 44 + const filename = getEmojiFilename(emojiName); 45 + if (filename) { 46 + // Found the correct filename, update src 47 + img.src = `/emojis/${filename}`; 48 + // Remove placeholder class if present 49 + img.classList.remove('emoji-placeholder'); 50 + // Remove the error handler since we have the correct path 51 + img.onerror = null; 52 + } else { 53 + // Emoji not found in map, try common extensions as fallback 54 + // This handles newly added emojis that aren't in the cached map yet 55 + img.src = `/emojis/${emojiName}.png`; 56 + img.onerror = function() { 57 + this.onerror = null; 58 + this.src = `/emojis/${emojiName}.gif`; 59 + }; 60 + img.classList.remove('emoji-placeholder'); 61 + } 62 + } 63 + 64 + // Update all emoji images on the page 65 + function updateAllEmojiImages() { 66 + const images = document.querySelectorAll('img[data-emoji-name]'); 67 + images.forEach(updateEmojiImage); 68 + } 69 + 70 + // Initialize on DOM ready 71 + async function initialize() { 72 + // Load the emoji map 73 + await loadEmojiMap(); 74 + // Update all existing emoji images 75 + updateAllEmojiImages(); 76 + 77 + // Set up a MutationObserver to handle dynamically added content 78 + const observer = new MutationObserver((mutations) => { 79 + mutations.forEach((mutation) => { 80 + mutation.addedNodes.forEach((node) => { 81 + if (node.nodeType === Node.ELEMENT_NODE) { 82 + // Check if the added node is an emoji image 83 + if (node.tagName === 'IMG' && node.getAttribute('data-emoji-name')) { 84 + updateEmojiImage(node); 85 + } 86 + // Also check descendants 87 + const images = node.querySelectorAll?.('img[data-emoji-name]'); 88 + images?.forEach(updateEmojiImage); 89 + } 90 + }); 91 + }); 92 + }); 93 + 94 + // Start observing the document body for changes 95 + observer.observe(document.body, { 96 + childList: true, 97 + subtree: true 98 + }); 99 + } 100 + 101 + // Export to global scope 102 + window.EmojiResolver = { 103 + loadEmojiMap, 104 + getEmojiFilename, 105 + updateEmojiImage, 106 + updateAllEmojiImages, 107 + initialize 108 + }; 109 + 110 + // Auto-initialize when DOM is ready 111 + if (document.readyState === 'loading') { 112 + document.addEventListener('DOMContentLoaded', initialize); 113 + } else { 114 + // DOM is already ready 115 + initialize(); 116 + } 117 + })();
+3
templates/base.html
··· 31 31 <!-- Markdown link renderer for statuses --> 32 32 <script src="/static/markdown.js"></script> 33 33 34 + <!-- Emoji Resolver for correct file extensions --> 35 + <script src="/static/emoji-resolver.js"></script> 36 + 34 37 <!-- Apply User Settings --> 35 38 <script> 36 39 // Apply saved settings immediately to prevent flash
+4 -3
templates/feed.html
··· 129 129 <span class="status-emoji"> 130 130 {% if status.status.starts_with("custom:") %} 131 131 {% let emoji_name = status.status.strip_prefix("custom:").unwrap() %} 132 - <img src="/emojis/{{emoji_name}}.png" alt="{{emoji_name}}" title="{{emoji_name}}" class="custom-emoji-display" 133 - onerror="this.onerror=null; this.src='/emojis/{{emoji_name}}.gif';"> 132 + <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" 133 + alt="{{emoji_name}}" title="{{emoji_name}}" class="custom-emoji-display emoji-placeholder" 134 + data-emoji-name="{{emoji_name}}"> 134 135 {% else %} 135 136 <span title="{{status.status}}">{{status.status}}</span> 136 137 {% endif %} ··· 989 990 let emojiHtml = ''; 990 991 if (status.status.startsWith('custom:')) { 991 992 const emojiName = status.status.substring(7); 992 - emojiHtml = `<img src="/emojis/${emojiName}.png" alt="${emojiName}" title="${emojiName}" class="custom-emoji-display" onerror="this.onerror=null; this.src='/emojis/${emojiName}.gif';">`; 993 + emojiHtml = `<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="${emojiName}" title="${emojiName}" class="custom-emoji-display emoji-placeholder" data-emoji-name="${emojiName}">`; 993 994 } else { 994 995 emojiHtml = `<span title="${status.status}">${status.status}</span>`; 995 996 }
+9 -6
templates/status.html
··· 103 103 <span class="status-emoji"> 104 104 {% if current.status.starts_with("custom:") %} 105 105 {% let emoji_name = current.status.strip_prefix("custom:").unwrap() %} 106 - <img src="/emojis/{{emoji_name}}.png" alt="{{emoji_name}}" title="{{emoji_name}}" class="custom-emoji-display" 107 - onerror="this.onerror=null; this.src='/emojis/{{emoji_name}}.gif';"> 106 + <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" 107 + alt="{{emoji_name}}" title="{{emoji_name}}" class="custom-emoji-display emoji-placeholder" 108 + data-emoji-name="{{emoji_name}}"> 108 109 {% else %} 109 110 <span title="{{ current.status }}">{{ current.status }}</span> 110 111 {% endif %} ··· 140 141 {% if let Some(current) = current_status.as_ref() %} 141 142 {% if current.status.starts_with("custom:") %} 142 143 {% let emoji_name = current.status.strip_prefix("custom:").unwrap() %} 143 - <img src="/emojis/{{emoji_name}}.png" alt="{{emoji_name}}" title="{{emoji_name}}" 144 - onerror="this.onerror=null; this.src='/emojis/{{emoji_name}}.gif';"> 144 + <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" 145 + alt="{{emoji_name}}" title="{{emoji_name}}" class="emoji-placeholder" 146 + data-emoji-name="{{emoji_name}}"> 145 147 {% else %} 146 148 <span title="{{ current.status }}">{{ current.status }}</span> 147 149 {% endif %} ··· 412 414 <span class="history-emoji"> 413 415 {% if status.status.starts_with("custom:") %} 414 416 {% let emoji_name = status.status.strip_prefix("custom:").unwrap() %} 415 - <img src="/emojis/{{emoji_name}}.png" alt="{{emoji_name}}" title="{{emoji_name}}" class="custom-emoji-display" 416 - onerror="this.onerror=null; this.src='/emojis/{{emoji_name}}.gif';"> 417 + <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" 418 + alt="{{emoji_name}}" title="{{emoji_name}}" class="custom-emoji-display emoji-placeholder" 419 + data-emoji-name="{{emoji_name}}"> 417 420 {% else %} 418 421 <span title="{{ status.status }}">{{ status.status }}</span> 419 422 {% endif %}