openvibers/openvibe.community Commits

Comments: one sign-in line when empty, Top and Newest only with two or more (#387)

OP OpenVibers committed 2 days ago · 418d035d3e · 2 files, +21 −11

Signed out with no comments, the composer spot reads "Be the first to comment."
with Sign in beside it, instead of a sign-in line and a second empty box. Top and
Newest show only when there is something to order. The head, the order pills and
the Community link are quieter; the thread sits flush in its panel.

Changes

ddiff --git a/public/css/inline-comments.css b/public/css/inline-comments.css
iindex cbfb011..0cccfb5 100644
---- a/public/css/inline-comments.css
++++ b/public/css/inline-comments.css
@@@ -1,15 +1,23 @@
++/* The inline thread on a paste page (the same look as Video and Pics): a quiet head (the title, then Top and Newest only when
++ there is something to order), the composer or one sign-in line, the comments, and a small link to the discussion. */
.ovc-thread { margin: 24px 0; }
--.ovc-head, .ovc-order, .ovc-comment header, .ovc-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
--.ovc-head { justify-content: space-between; }
++.ovc-head, .ovc-comment header, .ovc-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
++.ovc-head { justify-content: space-between; min-height: 40px; margin-bottom: 8px; }
++.ovc-head h2 { margin: 0; font-size: 1.125rem; }
++.ovc-signin, .ovc-none { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px; margin: 0; }
++.ovc-none { margin-top: 12px; opacity: .75; }
.ovc-comment { padding: 12px 0; border-bottom: 1px solid var(--border, #5555); }
.ovc-comment header img { border-radius: 50%; object-fit: cover; }
--.ovc-comment time, .ovc-community { font-size: .875em; opacity: .8; }
++.ovc-comment time { font-size: .875em; opacity: .8; }
.ovc-replies { margin-left: 24px; }
.ovc-actions form { display: inline; }
.ovc-attach { display: inline-block; margin: 4px 8px 4px 0; }
.ovc-attach--file { border: 1px solid currentColor; border-radius: 999px; padding: 4px 12px; }
.ovc-attach img { max-width: 120px; max-height: 90px; object-fit: cover; }
--.ovc-order { display: inline-flex; gap: 0; border: 1px solid currentColor; border-radius: 8px; overflow: hidden; }
--.ovc-order a { border: 0; border-radius: 0; }
--.ovc-order a + a { border-left: 1px solid currentColor; }
--.ovc-order .is-active { font-weight: 700; background: color-mix(in srgb, currentColor 12%, transparent); }
++.ovc-order { display: inline-flex; gap: 2px; padding: 2px; border-radius: 999px; background: color-mix(in srgb, currentColor 8%, transparent); }
++.ovc-order a { min-height: 36px; padding: 4px 14px; border: 0; border-radius: 999px; font-size: .875rem; }
++.ovc-order .is-active { font-weight: 700; background: color-mix(in srgb, currentColor 16%, transparent); }
++.ovc-community { margin: 12px 0 0; font-size: .875em; }
++.ovc-community a { color: inherit; opacity: .8; text-decoration: none; }
++.ovc-community a::after { content: ' \2192'; }
++.ovc-community a:hover, .ovc-community a:focus-visible { opacity: 1; text-decoration: underline; }
ddiff --git a/server/render/inline-thread.js b/server/render/inline-thread.js
iindex 640fbc7..e29c6d6 100644
---- a/server/render/inline-thread.js
++++ b/server/render/inline-thread.js
@@@ -1,7 +1,6 @@
'use strict';
/** Plain, server-rendered Community thread for the Video and Pics item pages. */
--const kit = require('openvibe-shared/kit');
const thread = require('openvibe-shared/thread');
const { escapeHtml: esc } = require('./highlight');
@@@ -34,7 +33,9 @@ function renderThread({ answer, base, post, remove, signedIn, attachmentsAllowed
const threadUrl = safeHttps(answer && answer.thread && answer.thread.url) || (/^cth_[A-Za-z0-9_-]{22}$/.test(accessId) ? `https://openvibe.community/c/${accessId}` : null);
const validReply = ID.test(String(replyId || '')) ? String(replyId) : null;
const validExpanded = ID.test(String(expandedId || '')) ? String(expandedId) : null;
-- const signIn = `<p class="ovc-signin">Sign in to comment. <a class="ovk-btn ovk-btn--primary" href="/auth/login?next=${encodeURIComponent(query(base, sort))}">Sign in</a></p>`;
++ // Signed out, the composer's spot is one line; with no comments yet that line is the whole empty state (no second
++ // box saying the same thing).
++ const signIn = `<p class="ovc-signin">${comments.length ? 'Sign in to comment.' : 'Be the first to comment.'} <a class="ovk-btn ovk-btn--primary" href="/auth/login?next=${encodeURIComponent(query(base, sort))}">Sign in</a></p>`;
const composer = (parent = null) => signedIn ? thread.composer({ action: post, name: 'message', label: 'Comment', placeholder: parent ? 'Write a reply…' : 'Add a comment…', value: draft, maxlength: 2000,
sendLabel: parent ? 'Reply' : 'Comment', hidden: parent ? { parent_id: parent } : {},
attach: attachmentsAllowed ? { name: 'files', accept: 'image/jpeg,image/png,image/gif,image/webp,application/pdf,text/plain' } : null,
@@@ -68,9 +69,10 @@ function renderThread({ answer, base, post, remove, signedIn, attachmentsAllowed
return `<article class="ovc-comment" id="comment-${id}"><header>${avatar ? `<img src="${esc(avatar)}" alt="" width="32" height="32" loading="lazy">` : ''}${profile ? `<a href="${profile}">${esc(name(c))}</a>` : `<span>${esc(name(c))}</span>`}${when.short ? `<time datetime="${esc(c.created_at)}" title="${esc(when.full)}">${esc(when.short)}</time>` : ''}</header>${target}${gone ? '<p>Comment removed</p>' : `<p>${esc(c.message || '')}</p>${attachments(c)}`}${controls}${validReply === id ? `<div data-ovc-composer-slot>${composer(id)}</div>` : ''}${replies}</article>`;
};
const hasReplySlot = validReply && comments.some((c) => String(c.id) === validReply || (validExpanded === String(c.id) && Array.isArray(expandedReplies) ? expandedReplies : c.replies || []).some((r) => String(r.id) === validReply));
-- const order = `<nav class="ovc-order" aria-label="Comment order">${[['top', 'Top'], ['new', 'Newest']].map(([key, label]) => `<a class="ovk-btn ovk-btn--quiet${sort === key ? ' is-active' : ''}" href="${query(base, key)}"${sort === key ? ' aria-current="page"' : ''} data-ovc-order>${label}</a>`).join('')}</nav>`;
++ // Top and Newest only when there is something to put in order.
++ const order = comments.length < 2 && !(answer && answer.next_cursor) ? '' : `<nav class="ovc-order" aria-label="Comment order">${[['top', 'Top'], ['new', 'Newest']].map(([key, label]) => `<a class="ovk-btn ovk-btn--quiet${sort === key ? ' is-active' : ''}" href="${query(base, key)}"${sort === key ? ' aria-current="page"' : ''} data-ovc-order>${label}</a>`).join('')}</nav>`;
const next = answer && answer.next_cursor ? `<a class="ovk-btn ovk-btn--quiet ovc-more" href="${query(base, sort, { after: answer.next_cursor })}" data-ovc-order>More comments</a>` : '';
-- return `<section class="ovc-thread" id="comments" data-ovc-thread><div class="ovc-head"><h2>Comments</h2>${order}</div>${error ? `<p role="alert">${esc(error)}</p>` : ''}${!hasReplySlot ? `<div data-ovc-composer-slot>${composer()}</div>` : ''}${comments.length ? `<div class="ovc-list">${comments.map((c) => comment(c)).join('')}</div>` : kit.empty({ title: 'Be the first to comment' })}${next}${threadUrl ? `<p class="ovc-community"><a href="${esc(threadUrl)}">Discuss on OpenVibe.Community</a></p>` : ''}</section>`;
++ return `<section class="ovc-thread" id="comments" data-ovc-thread><div class="ovc-head"><h2>Comments</h2>${order}</div>${error ? `<p role="alert">${esc(error)}</p>` : ''}${!hasReplySlot ? `<div data-ovc-composer-slot>${composer()}</div>` : ''}${comments.length ? `<div class="ovc-list">${comments.map((c) => comment(c)).join('')}</div>` : (signedIn ? '<p class="ovc-none">Be the first to comment.</p>' : '')}${next}${threadUrl ? `<p class="ovc-community"><a href="${esc(threadUrl)}">Discuss on OpenVibe.Community</a></p>` : ''}</section>`;
}
module.exports = { renderThread, MEDIA_ID, ID };