Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions static/emoji-resolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Emoji Resolver Module - Handles mapping emoji names to correct filenames
(function() {
'use strict';

// Cache for emoji name -> filename mapping
let emojiMap = null;
let loadPromise = null;

// Load emoji mapping from API
async function loadEmojiMap() {
if (emojiMap) return emojiMap;
if (loadPromise) return loadPromise;

loadPromise = fetch('/api/custom-emojis')
.then(response => response.json())
.then(data => {
if (!Array.isArray(data)) {
console.error('Invalid emoji data received');
return new Map();
}
emojiMap = new Map(data.map(emoji => [emoji.name, emoji.filename]));
return emojiMap;
})
.catch(err => {
console.error('Failed to load emoji map:', err);
emojiMap = new Map();
return emojiMap;
});

return loadPromise;
}

// Get the correct emoji filename for a given name
function getEmojiFilename(emojiName) {
if (!emojiMap) return null;
return emojiMap.get(emojiName);
}

// Update a single emoji image element
function updateEmojiImage(img) {
const emojiName = img.getAttribute('data-emoji-name');
if (!emojiName) return;

const filename = getEmojiFilename(emojiName);
if (filename) {
// Found the correct filename, update src
img.src = `/emojis/${filename}`;
// Remove placeholder class if present
img.classList.remove('emoji-placeholder');
// Remove the error handler since we have the correct path
img.onerror = null;
} else {
// Emoji not found in map, try common extensions as fallback
// This handles newly added emojis that aren't in the cached map yet
img.src = `/emojis/${emojiName}.png`;
img.onerror = function() {
this.onerror = null;
this.src = `/emojis/${emojiName}.gif`;
};
img.classList.remove('emoji-placeholder');
}
}

// Update all emoji images on the page
function updateAllEmojiImages() {
const images = document.querySelectorAll('img[data-emoji-name]');
images.forEach(updateEmojiImage);
}

// Initialize on DOM ready
async function initialize() {
// Load the emoji map
await loadEmojiMap();
// Update all existing emoji images
updateAllEmojiImages();

// Set up a MutationObserver to handle dynamically added content
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
// Check if the added node is an emoji image
if (node.tagName === 'IMG' && node.getAttribute('data-emoji-name')) {
updateEmojiImage(node);
}
// Also check descendants
const images = node.querySelectorAll?.('img[data-emoji-name]');
images?.forEach(updateEmojiImage);
}
});
});
});

// Start observing the document body for changes
observer.observe(document.body, {
childList: true,
subtree: true
});
}

// Export to global scope
window.EmojiResolver = {
loadEmojiMap,
getEmojiFilename,
updateEmojiImage,
updateAllEmojiImages,
initialize
};

// Auto-initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
} else {
// DOM is already ready
initialize();
}
})();
3 changes: 3 additions & 0 deletions templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
<!-- Markdown link renderer for statuses -->
<script src="/static/markdown.js"></script>

<!-- Emoji Resolver for correct file extensions -->
<script src="/static/emoji-resolver.js"></script>

<!-- Apply User Settings -->
<script>
// Apply saved settings immediately to prevent flash
Expand Down
7 changes: 4 additions & 3 deletions templates/feed.html
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,9 @@ <h2>recent updates</h2>
<span class="status-emoji">
{% if status.status.starts_with("custom:") %}
{% let emoji_name = status.status.strip_prefix("custom:").unwrap() %}
<img src="/emojis/{{emoji_name}}.png" alt="{{emoji_name}}" title="{{emoji_name}}" class="custom-emoji-display"
onerror="this.onerror=null; this.src='/emojis/{{emoji_name}}.gif';">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
alt="{{emoji_name}}" title="{{emoji_name}}" class="custom-emoji-display emoji-placeholder"
data-emoji-name="{{emoji_name}}">
{% else %}
<span title="{{status.status}}">{{status.status}}</span>
{% endif %}
Expand Down Expand Up @@ -989,7 +990,7 @@ <h2>recent updates</h2>
let emojiHtml = '';
if (status.status.startsWith('custom:')) {
const emojiName = status.status.substring(7);
emojiHtml = `<img src="/emojis/${emojiName}.png" alt="${emojiName}" title="${emojiName}" class="custom-emoji-display" onerror="this.onerror=null; this.src='/emojis/${emojiName}.gif';">`;
emojiHtml = `<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="${emojiName}" title="${emojiName}" class="custom-emoji-display emoji-placeholder" data-emoji-name="${emojiName}">`;
} else {
emojiHtml = `<span title="${status.status}">${status.status}</span>`;
}
Expand Down
26 changes: 19 additions & 7 deletions templates/status.html
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,9 @@ <h1><a href="https://bsky.app/profile/{{ handle }}" target="_blank" rel="noopene
<span class="status-emoji">
{% if current.status.starts_with("custom:") %}
{% let emoji_name = current.status.strip_prefix("custom:").unwrap() %}
<img src="/emojis/{{emoji_name}}.png" alt="{{emoji_name}}" title="{{emoji_name}}" class="custom-emoji-display"
onerror="this.onerror=null; this.src='/emojis/{{emoji_name}}.gif';">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
alt="{{emoji_name}}" title="{{emoji_name}}" class="custom-emoji-display emoji-placeholder"
data-emoji-name="{{emoji_name}}">
{% else %}
<span title="{{ current.status }}">{{ current.status }}</span>
{% endif %}
Expand Down Expand Up @@ -140,8 +141,9 @@ <h1><a href="https://bsky.app/profile/{{ handle }}" target="_blank" rel="noopene
{% if let Some(current) = current_status.as_ref() %}
{% if current.status.starts_with("custom:") %}
{% let emoji_name = current.status.strip_prefix("custom:").unwrap() %}
<img src="/emojis/{{emoji_name}}.png" alt="{{emoji_name}}" title="{{emoji_name}}"
onerror="this.onerror=null; this.src='/emojis/{{emoji_name}}.gif';">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
alt="{{emoji_name}}" title="{{emoji_name}}" class="emoji-placeholder"
data-emoji-name="{{emoji_name}}">
{% else %}
<span title="{{ current.status }}">{{ current.status }}</span>
{% endif %}
Expand Down Expand Up @@ -412,8 +414,9 @@ <h3>recent</h3>
<span class="history-emoji">
{% if status.status.starts_with("custom:") %}
{% let emoji_name = status.status.strip_prefix("custom:").unwrap() %}
<img src="/emojis/{{emoji_name}}.png" alt="{{emoji_name}}" title="{{emoji_name}}" class="custom-emoji-display"
onerror="this.onerror=null; this.src='/emojis/{{emoji_name}}.gif';">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
alt="{{emoji_name}}" title="{{emoji_name}}" class="custom-emoji-display emoji-placeholder"
data-emoji-name="{{emoji_name}}">
{% else %}
<span title="{{ status.status }}">{{ status.status }}</span>
{% endif %}
Expand Down Expand Up @@ -2243,10 +2246,19 @@ <h3>recent</h3>
button.dataset.name = emoji.name;

const img = document.createElement('img');
img.src = `/emojis/${emoji.filename}`;
// Use placeholder initially to avoid 404s
img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
img.dataset.emojiName = emoji.name;
img.dataset.emojiFilename = emoji.filename;
img.alt = emoji.name;
img.title = emoji.name;
button.appendChild(img);

// Load the actual image after a brief delay to let resolver initialize
requestAnimationFrame(() => {
img.src = `/emojis/${emoji.filename}`;
});

return button;
};

Expand Down