diff --git a/public/s/js/sidebar-activity.js b/public/s/js/sidebar-activity.js index c7fec29..f919c37 100644 --- a/public/s/js/sidebar-activity.js +++ b/public/s/js/sidebar-activity.js @@ -504,7 +504,7 @@ ${timeStr} -
${displayContent}${attachmentsHtml}${pollHtml}
+
${displayContent}${attachmentsHtml}${pollHtml}
${itemPreview} `; @@ -544,11 +544,8 @@ // Write phase: perform DOM updates after all reads are completed. // Visibility of the button is controlled entirely by the CSS rule on - // .comment-content.has-overflow > .read-more-btn — no btn.style.display here. + // .comment-content.has-overflow > .read-more-btn — JS only toggles the class. results.forEach(({ container, btn, isExpanded, scrollHeight, clientHeight, hasUnloadedImages }) => { - // Clear any leftover inline display style (e.g. from the HTML template's - // style="display:block") so the CSS class-based rule is the single source of truth. - btn.style.display = ''; if (isExpanded) { btn.textContent = window.f0ckI18n?.sidebar_see_less || 'see less'; @@ -638,19 +635,17 @@ } attachMediaLoadListeners(container); // has-overflow is set by default in the HTML template so all comments start - // clamped with the "read more" button visible. We must NOT run checkOverflow - // synchronously here — the browser has not yet laid out the freshly injected - // innerHTML, so scrollHeight / clientHeight are 0 and checkOverflow would - // incorrectly hide every button. Instead we defer to after the first layout - // frame so measurements are accurate and short comments get their buttons - // removed while long comments keep them visible from the very first paint. - requestAnimationFrame(() => { - checkOverflow(); - // After 1s, re-check everything — by then even slow ctrl+F5 images will - // have loaded and been laid out, so scrollHeight is accurate for all - // image comments. - setTimeout(checkOverflow, 1000); - }); + // clamped with the "read more" button visible (CSS handles this via the + // .has-overflow > .read-more-btn rule). We intentionally do NOT run + // checkOverflow eagerly — on production, layout measurements are unreliable + // even inside requestAnimationFrame and the function would incorrectly + // remove has-overflow from comments that genuinely overflow, causing the + // button to disappear and re-appear. Instead we only run checkOverflow + // after a comfortable delay so short comments get their buttons cleaned up + // once layout is fully settled. + setTimeout(checkOverflow, 300); + // Safety-net re-check: images may still be loading at 300ms. + setTimeout(checkOverflow, 1500); fetchSidebarYoutubeTitles(container); // Auto-play converted GIF videos and webm emoji stickers container.querySelectorAll('video.autoplay-gif').forEach(v => { v.autoplay = true; v.muted = true; v.play().catch(() => { v.addEventListener('canplay', () => v.play().catch(() => { }), { once: true }); }); }); @@ -773,13 +768,10 @@ fetchSidebarYoutubeTitles(node); playSidebarEmojiVideos(node); }); - // Defer overflow checks to after layout so measurements are accurate. - requestAnimationFrame(() => { - addedNodes.forEach(node => checkOverflow(node)); - // Deferred re-check for image comments — mirrors renderFromCache behaviour. - // Images in newly appended nodes may not be laid out yet at append time. - setTimeout(() => addedNodes.forEach(node => checkOverflow(node)), 1000); - }); + // Defer overflow checks so layout measurements are accurate. + setTimeout(() => addedNodes.forEach(node => checkOverflow(node)), 300); + // Safety-net re-check for image comments whose layout settles later. + setTimeout(() => addedNodes.forEach(node => checkOverflow(node)), 1500); // Auto-play converted GIF videos and webm emoji stickers container.querySelectorAll('video.autoplay-gif').forEach(v => { v.autoplay = true; v.muted = true; v.play().catch(() => { v.addEventListener('canplay', () => v.play().catch(() => { }), { once: true }); }); }); } @@ -844,7 +836,7 @@ node.classList.add('new-item-fade'); container.prepend(node); attachMediaLoadListeners(node); - requestAnimationFrame(() => checkOverflow(node)); + setTimeout(() => checkOverflow(node), 300); fetchSidebarYoutubeTitles(container); playSidebarEmojiVideos(node); } @@ -896,7 +888,7 @@ void el.offsetWidth; el.classList.add('new-item-fade'); attachMediaLoadListeners(inner); - requestAnimationFrame(() => checkOverflow(inner)); + setTimeout(() => checkOverflow(inner), 300); fetchSidebarYoutubeTitles(el); // Auto-play converted GIF videos inner.querySelectorAll('video.autoplay-gif').forEach(v => { v.autoplay = true; v.muted = true; v.play().catch(() => { v.addEventListener('canplay', () => v.play().catch(() => { }), { once: true }); }); });