diff --git a/public/s/js/comments.js b/public/s/js/comments.js index 4fc11c4..fddcd7e 100644 --- a/public/s/js/comments.js +++ b/public/s/js/comments.js @@ -79,7 +79,9 @@ class CommentSystem { this.commentCache = new Map(); this._anchorScrollDone = false; // true after the first hash-anchor scroll on initial load - this.loadEmojis(); // Always load emojis for previews + // Defer emoji loading — only fetch if the page actually has :emoji: patterns + // (avoids a full API round-trip on every item page that has no custom emojis) + this._emojiLoadScheduled = false; this.setupHoverPreviews(); if (this.itemId) { @@ -224,7 +226,12 @@ class CommentSystem { _f0ckDebug('[CommentSystem] Instance destroyed'); } - async loadEmojis() { + /** + * Load custom emoji map from the API. + * Lazy by default — only fetches if the current page actually contains :emoji: patterns. + * Pass force=true to bypass the page-scan check (e.g. after SSE emojis_updated). + */ + async loadEmojis(force = false) { if (CommentSystem.emojiCache) { this.customEmojis = CommentSystem.emojiCache; // Immediate dispatch if already ready @@ -232,6 +239,19 @@ class CommentSystem { return; } if (CommentSystem.loadingEmojis) return; + + // Skip the fetch entirely if there are no :emoji: shortcodes visible on this page. + // This avoids an unnecessary API round-trip on every item page where custom emojis aren't used. + if (!force) { + const textContent = this.container ? this.container.textContent : document.body.textContent; + if (!/:([a-z0-9_]+):/.test(textContent)) { + _f0ckDebug('[CommentSystem] No emoji patterns found on page, skipping emoji fetch.'); + // Still emit ready so callers (e.g. emoji picker) don't hang + window.dispatchEvent(new CustomEvent('f0ck:emojis_ready', { detail: {} })); + return; + } + } + CommentSystem.loadingEmojis = true; try { @@ -245,9 +265,6 @@ class CommentSystem { CommentSystem.emojiCache = this.customEmojis; _f0ckDebug('Loaded Emojis:', this.customEmojis); - // Preload images to prevent NS Binding Aborted errors - this.preloadEmojiImages(); - if (this.container && this.lastData) { const state = this.saveState(); // Invalidate data-raw on any comment content that contains emoji shortcodes, @@ -282,17 +299,6 @@ class CommentSystem { } } - preloadEmojiImages() { - // Preload all emoji images into browser cache - if (!this.customEmojis) return; - - Object.values(this.customEmojis).forEach(url => { - const img = new Image(); - img.src = url; - // No need to append to DOM, just loading into cache - }); - } - // ... renderEmoji(match, name) { @@ -1312,6 +1318,13 @@ class CommentSystem { const mainInput = this.container.querySelector('.main-input'); if (mainInput) this.setupEmojiPicker(mainInput); + + // Lazy emoji load: scan the just-rendered comment text for :emoji: patterns. + // Only fires an API request if at least one shortcode is actually present, + // avoiding a full round-trip on pages with no custom emojis. + if (!CommentSystem.emojiCache && !CommentSystem.loadingEmojis) { + this.loadEmojis(); // will skip internally if no patterns found + } } syncSubscribeButton(isSubscribed) { @@ -3599,7 +3612,7 @@ class CommentSystem { if (!cs) return; CommentSystem.emojiCache = null; CommentSystem.loadingEmojis = false; - cs.loadEmojis(); + cs.loadEmojis(true); // force=true: bypass page-scan, admin just changed emojis }); // Shortcut 'c' to toggle comments @@ -3842,7 +3855,13 @@ class CommentSystem { hideAC(); }; - textarea.addEventListener('input', () => renderAC()); + textarea.addEventListener('input', () => { + // Lazy-load emojis if user starts typing ':' (emoji autocomplete) but cache is empty + if (!CommentSystem.emojiCache && !CommentSystem.loadingEmojis && getColon()) { + this.loadEmojis(true); // force=true: user explicitly wants to pick an emoji + } + renderAC(); + }); textarea.addEventListener('keydown', (e) => { if (autocomplete.style.display === 'none') return; if (e.key === 'ArrowDown') { @@ -3958,9 +3977,53 @@ class CommentSystem { let picker = null; let closeHandler = null; + const buildPickerContent = () => { + if (!picker) return; + picker.innerHTML = ''; + if (this.customEmojis && Object.keys(this.customEmojis).length > 0) { + Object.keys(this.customEmojis).forEach(name => { + const url = this.customEmojis[name]; + const img = document.createElement('img'); + img.src = url; + img.title = `:${name}:`; + img.loading = 'lazy'; // Use native lazy loading + + // Add error handling for failed loads + img.onerror = () => { + console.warn(`Failed to load emoji: ${name}`); + img.style.display = 'none'; + }; + + img.onclick = (ev) => { + ev.stopPropagation(); + const pos = textarea.selectionStart ?? textarea.value.length; + const val = textarea.value; + textarea.value = val.slice(0, pos) + `:${name}:` + val.slice(pos); + textarea.focus(); + // Move cursor after the inserted emoji + const newPos = pos + name.length + 2; + textarea.setSelectionRange(newPos, newPos); + }; + picker.appendChild(img); + }); + } else { + picker.innerHTML = '