hausdavid
This commit is contained in:
@@ -79,7 +79,9 @@ class CommentSystem {
|
|||||||
this.commentCache = new Map();
|
this.commentCache = new Map();
|
||||||
this._anchorScrollDone = false; // true after the first hash-anchor scroll on initial load
|
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();
|
this.setupHoverPreviews();
|
||||||
|
|
||||||
if (this.itemId) {
|
if (this.itemId) {
|
||||||
@@ -224,7 +226,12 @@ class CommentSystem {
|
|||||||
_f0ckDebug('[CommentSystem] Instance destroyed');
|
_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) {
|
if (CommentSystem.emojiCache) {
|
||||||
this.customEmojis = CommentSystem.emojiCache;
|
this.customEmojis = CommentSystem.emojiCache;
|
||||||
// Immediate dispatch if already ready
|
// Immediate dispatch if already ready
|
||||||
@@ -232,6 +239,19 @@ class CommentSystem {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (CommentSystem.loadingEmojis) 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;
|
CommentSystem.loadingEmojis = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -245,9 +265,6 @@ class CommentSystem {
|
|||||||
CommentSystem.emojiCache = this.customEmojis;
|
CommentSystem.emojiCache = this.customEmojis;
|
||||||
_f0ckDebug('Loaded Emojis:', this.customEmojis);
|
_f0ckDebug('Loaded Emojis:', this.customEmojis);
|
||||||
|
|
||||||
// Preload images to prevent NS Binding Aborted errors
|
|
||||||
this.preloadEmojiImages();
|
|
||||||
|
|
||||||
if (this.container && this.lastData) {
|
if (this.container && this.lastData) {
|
||||||
const state = this.saveState();
|
const state = this.saveState();
|
||||||
// Invalidate data-raw on any comment content that contains emoji shortcodes,
|
// 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) {
|
renderEmoji(match, name) {
|
||||||
@@ -1312,6 +1318,13 @@ class CommentSystem {
|
|||||||
|
|
||||||
const mainInput = this.container.querySelector('.main-input');
|
const mainInput = this.container.querySelector('.main-input');
|
||||||
if (mainInput) this.setupEmojiPicker(mainInput);
|
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) {
|
syncSubscribeButton(isSubscribed) {
|
||||||
@@ -3599,7 +3612,7 @@ class CommentSystem {
|
|||||||
if (!cs) return;
|
if (!cs) return;
|
||||||
CommentSystem.emojiCache = null;
|
CommentSystem.emojiCache = null;
|
||||||
CommentSystem.loadingEmojis = false;
|
CommentSystem.loadingEmojis = false;
|
||||||
cs.loadEmojis();
|
cs.loadEmojis(true); // force=true: bypass page-scan, admin just changed emojis
|
||||||
});
|
});
|
||||||
|
|
||||||
// Shortcut 'c' to toggle comments
|
// Shortcut 'c' to toggle comments
|
||||||
@@ -3842,7 +3855,13 @@ class CommentSystem {
|
|||||||
hideAC();
|
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) => {
|
textarea.addEventListener('keydown', (e) => {
|
||||||
if (autocomplete.style.display === 'none') return;
|
if (autocomplete.style.display === 'none') return;
|
||||||
if (e.key === 'ArrowDown') {
|
if (e.key === 'ArrowDown') {
|
||||||
@@ -3958,9 +3977,53 @@ class CommentSystem {
|
|||||||
let picker = null;
|
let picker = null;
|
||||||
let closeHandler = 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 = '<div style="padding:5px;color:white;font-size:0.8em;">No emojis found</div>';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
trigger.addEventListener('click', (e) => {
|
trigger.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Always kick off a load on first click (lazy — noop if already loading/cached)
|
||||||
|
if (!CommentSystem.emojiCache && !CommentSystem.loadingEmojis) {
|
||||||
|
this.loadEmojis(true).then(() => {
|
||||||
|
// Rebuild picker content once emojis arrive (if picker already open)
|
||||||
|
if (picker && picker.style.display !== 'none') {
|
||||||
|
buildPickerContent();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// If picker already exists, toggle visibility
|
// If picker already exists, toggle visibility
|
||||||
if (picker) {
|
if (picker) {
|
||||||
const isVisible = picker.style.display !== 'none';
|
const isVisible = picker.style.display !== 'none';
|
||||||
@@ -3971,6 +4034,8 @@ class CommentSystem {
|
|||||||
closeHandler = null;
|
closeHandler = null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Rebuild content in case emojis loaded since the picker was first created
|
||||||
|
buildPickerContent();
|
||||||
picker.style.display = ''; // Reset to CSS default (flex)
|
picker.style.display = ''; // Reset to CSS default (flex)
|
||||||
requestAnimationFrame(() => picker.scrollIntoView({ behavior: 'smooth', block: 'nearest' }));
|
requestAnimationFrame(() => picker.scrollIntoView({ behavior: 'smooth', block: 'nearest' }));
|
||||||
closeHandler = (ev) => {
|
closeHandler = (ev) => {
|
||||||
@@ -4027,34 +4092,18 @@ class CommentSystem {
|
|||||||
}
|
}
|
||||||
}, { passive: false });
|
}, { passive: false });
|
||||||
|
|
||||||
if (this.customEmojis && Object.keys(this.customEmojis).length > 0) {
|
if (CommentSystem.emojiCache) {
|
||||||
Object.keys(this.customEmojis).forEach(name => {
|
// Emojis already cached — populate immediately
|
||||||
const url = this.customEmojis[name];
|
buildPickerContent();
|
||||||
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 {
|
} else {
|
||||||
picker.innerHTML = '<div style="padding:5px;color:white;font-size:0.8em;">No emojis found</div>';
|
// Show a loading indicator while fetch is in-flight
|
||||||
|
picker.innerHTML = '<div style="padding:5px;color:white;font-size:0.8em;">Loading...</div>';
|
||||||
|
// Rebuild once the fetch completes (loadEmojis was already triggered above)
|
||||||
|
const onReady = () => {
|
||||||
|
window.removeEventListener('f0ck:emojis_ready', onReady);
|
||||||
|
buildPickerContent();
|
||||||
|
};
|
||||||
|
window.addEventListener('f0ck:emojis_ready', onReady, { once: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
container.appendChild(picker);
|
container.appendChild(picker);
|
||||||
|
|||||||
@@ -6017,7 +6017,8 @@ class NotificationSystem {
|
|||||||
this._initialized = true;
|
this._initialized = true;
|
||||||
|
|
||||||
if (this.bell && this.dropdown && this.list) {
|
if (this.bell && this.dropdown && this.list) {
|
||||||
this.loadEmojis();
|
// Emojis are only needed for rendering notification text — defer fetch until dropdown first opens
|
||||||
|
this._emojisLoaded = false;
|
||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
this.poll();
|
this.poll();
|
||||||
this.pollDebounced = this.debounce(() => this.poll(), 500);
|
this.pollDebounced = this.debounce(() => this.poll(), 500);
|
||||||
@@ -6246,7 +6247,13 @@ class NotificationSystem {
|
|||||||
}
|
}
|
||||||
} else if (data.type === 'emojis_updated') {
|
} else if (data.type === 'emojis_updated') {
|
||||||
window.f0ckDebug("[SSE] Emojis updated, refreshing caches...");
|
window.f0ckDebug("[SSE] Emojis updated, refreshing caches...");
|
||||||
|
// If dropdown was never opened, just reset the flag so next open fetches fresh data.
|
||||||
|
// If it was already opened (emojis in use), re-fetch immediately.
|
||||||
|
if (this._emojisLoaded) {
|
||||||
this.loadEmojis();
|
this.loadEmojis();
|
||||||
|
} else {
|
||||||
|
this._emojisLoaded = false; // ensure re-fetch on next open
|
||||||
|
}
|
||||||
window.dispatchEvent(new CustomEvent('f0ck:emojis_updated'));
|
window.dispatchEvent(new CustomEvent('f0ck:emojis_updated'));
|
||||||
} else if (data.type === 'motd') {
|
} else if (data.type === 'motd') {
|
||||||
window.f0ckDebug(`[SSE] MOTD update received:`, data.data.motd);
|
window.f0ckDebug(`[SSE] MOTD update received:`, data.data.motd);
|
||||||
@@ -6302,7 +6309,7 @@ class NotificationSystem {
|
|||||||
} else if (data.type === 'emojis_updated') {
|
} else if (data.type === 'emojis_updated') {
|
||||||
window.f0ckDebug(`[SSE] Emoji update event received`);
|
window.f0ckDebug(`[SSE] Emoji update event received`);
|
||||||
if (window.commentSystem && typeof window.commentSystem.loadEmojis === 'function') {
|
if (window.commentSystem && typeof window.commentSystem.loadEmojis === 'function') {
|
||||||
window.commentSystem.loadEmojis();
|
window.commentSystem.loadEmojis(true); // force=true: admin updated emojis
|
||||||
}
|
}
|
||||||
// Global dispatch for other listeners (e.g. Admin Dashboard)
|
// Global dispatch for other listeners (e.g. Admin Dashboard)
|
||||||
document.dispatchEvent(new Event('f0ck:emojis_updated'));
|
document.dispatchEvent(new Event('f0ck:emojis_updated'));
|
||||||
@@ -6494,6 +6501,11 @@ class NotificationSystem {
|
|||||||
this.bell.addEventListener('click', (e) => {
|
this.bell.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
// Lazy-load emojis on first open — no need to fetch them on every page load
|
||||||
|
if (!this._emojisLoaded) {
|
||||||
|
this._emojisLoaded = true;
|
||||||
|
this.loadEmojis();
|
||||||
|
}
|
||||||
this.toggle();
|
this.toggle();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user