diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js
index f779a65..fb655f5 100644
--- a/public/s/js/f0ckm.js
+++ b/public/s/js/f0ckm.js
@@ -3147,7 +3147,8 @@ window.cancelAnimFrame = (function () {
if (visualizerRafId) window.cancelAnimFrame(visualizerRafId);
- const media = document.querySelectorAll('video:not(#gchat-messages video), audio:not(#gchat-messages audio)');
+ const media = [...document.querySelectorAll('video:not(#gchat-messages video), audio:not(#gchat-messages audio)')]
+ .filter(m => !m.classList.contains('emoji') || !m.closest('.sidebar-activity'));
media.forEach(m => {
try {
diff --git a/public/s/js/sidebar-activity.js b/public/s/js/sidebar-activity.js
index 2fe7923..3b65396 100644
--- a/public/s/js/sidebar-activity.js
+++ b/public/s/js/sidebar-activity.js
@@ -511,7 +511,7 @@
-
+
${itemPreview}
`;
@@ -644,13 +644,14 @@
container.appendChild(ioSentinel);
}
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.
+ // has-overflow is now set per-comment by the server (via is_long) so the
+ // correct clamped/unclamped state is baked into the HTML on first paint.
+ // checkOverflow still runs deferred to handle two edge cases:
+ // 1. Comments with images whose heights are 0 at inject time — the server
+ // correctly marks them is_long:true, but images that load extra-tall
+ // still need a re-check after load (handled by attachMediaLoadListeners).
+ // 2. Any mis-estimates: content that the heuristic got wrong is corrected
+ // after layout so measurements are accurate.
requestAnimationFrame(() => {
checkOverflow();
// After 1s, re-check everything — by then even slow ctrl+F5 images will
@@ -843,7 +844,11 @@
// Update DOM if visible
if (container) {
- const html = renderActivityItem(newItem);
+ // is_long may be provided by the server (SSE payload) or absent for legacy events.
+ // Default to true so the button is always visible; checkOverflow will hide it
+ // if the rendered content is actually short.
+ const itemWithLong = { ...newItem, is_long: newItem.is_long !== false };
+ const html = renderActivityItem(itemWithLong);
const temp = document.createElement('div');
temp.innerHTML = html;
const node = temp.firstElementChild;
diff --git a/src/inc/routes/comments.mjs b/src/inc/routes/comments.mjs
index a331d36..1cd0ad0 100644
--- a/src/inc/routes/comments.mjs
+++ b/src/inc/routes/comments.mjs
@@ -610,6 +610,11 @@ export default (router, tpl) => {
db.notify('comments', JSON.stringify(livePayload));
// 2. Sidebar activity update
+ // Compute is_long using the full content (not the truncated notifyBody) so the
+ // sidebar renders the correct clamped state immediately on first paint.
+ const activityIsLong = content.length > 120
+ || content.split('\n').length > 2
+ || activityFiles.length > 0;
db.notify('activity', JSON.stringify({
user_id: req.session.id,
item_id: item_id,
@@ -623,7 +628,8 @@ export default (router, tpl) => {
username: req.session.user,
username_color: req.session.username_color,
display_name: req.session.display_name || null,
- files: activityFiles
+ files: activityFiles,
+ is_long: activityIsLong
}));
// Automatically subscribe user to the thread
@@ -1090,14 +1096,24 @@ export default (router, tpl) => {
else if (c.rating_tag_id == 2) { ratingLabel = 'NSFW'; ratingClass = 'nsfw'; }
else if (c.rating_tag_id == (cfg.nsfl_tag_id || 3)) { ratingLabel = 'NSFL'; ratingClass = 'nsfl'; }
+ const commentContent = (c.content || '').trim();
+ const commentFiles = filesMap.get(c.id) || [];
+ // Compute overflow hint: true if content is likely taller than the 80px clamp.
+ // ~120 chars ≈ 2-3 wrapped lines in the sidebar; any newlines > 2 or file
+ // attachments (images/video) will also push height above the limit.
+ const isLong = commentContent.length > 120
+ || commentContent.split('\n').length > 2
+ || commentFiles.length > 0;
+
return {
...c,
- content: (c.content || '').trim(),
+ content: commentContent,
username_color: c.username_color,
item_rating_class: ratingClass,
item_rating_label: ratingLabel,
- files: filesMap.get(c.id) || [],
- poll: pollMap.get(c.id) || null
+ files: commentFiles,
+ poll: pollMap.get(c.id) || null,
+ is_long: isLong
// created_at stays as the raw ISO timestamp so the frontend f0ckTimeAgo can localize it
};
});