Changeset 59f0170 in Klonkt


Ignore:
Timestamp:
06/24/2026 02:26:32 PM (3 months ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
dc8e9bd
Parents:
2e9773f
Message:

refactor(comments): remove native comments — social is fediverse-only

Removes routes/comments.js + admin-comments.js (+ mounts), the comment section &
reply UI on posts, comment loading in the post route, the admin moderation page +
links, and the per-site comment-moderation settings. The 'From the fediverse'
section is now the post's comment area. The .comment CSS stays (reused there);
the comments table is left in place (dead) so old-data cascade-deletes still work.

Co-Authored-By: Claude <noreply@…>

Location:
src
Files:
3 deleted
6 edited

Legend:

Unmodified
Added
Removed
  • src/routes/posts.js

    r2e9773f r59f0170  
    710710  }
    711711
    712   // Comments: top-level + replies. Two-pass build: fetch all approved
    713   // comments for the post, then group replies under their parent.
    714   const commentRows = db.prepare(`
    715     SELECT c.id, c.parent_comment_id, c.content, c.status, c.created_at,
    716            c.author_id, u.username AS author_username,
    717            COALESCE(u.avatar_url, (SELECT profile_photo FROM sites WHERE owner_id = u.id AND profile_photo IS NOT NULL ORDER BY is_primary DESC, created_at ASC LIMIT 1)) AS author_avatar
    718     FROM comments c JOIN users u ON u.id = c.author_id
    719     WHERE c.post_id = ? AND c.status = 'approved'
    720     ORDER BY c.created_at ASC
    721   `).all(post.id);
    722   const topLevel = [];
    723   const repliesById = new Map();
    724   for (const c of commentRows) {
    725     if (c.parent_comment_id) {
    726       if (!repliesById.has(c.parent_comment_id)) repliesById.set(c.parent_comment_id, []);
    727       repliesById.get(c.parent_comment_id).push(c);
    728     } else {
    729       topLevel.push(c);
    730     }
    731   }
    732   for (const c of topLevel) c.replies = repliesById.get(c.id) || [];
    733   const totalComments = commentRows.length;
     712  // Native comments removed: social interaction is fediverse-only (see the
     713  // "From the fediverse" section below).
    734714
    735715  // Prev / next chronological (kept for back-compat — "post-nav" feature
     
    818798    olderPost,
    819799    relatedPosts,
    820     comments: topLevel,
    821     totalComments,
    822800    fediverse,
    823801    canManageSite,
  • src/server.js

    r2e9773f r59f0170  
    3232import adminSitesRoutes from './routes/admin-sites.js';
    3333import adminUsersRoutes from './routes/admin-users.js';
    34 import adminCommentsRoutes from './routes/admin-comments.js';
    3534import adminSettingsRoutes from './routes/admin-settings.js';
    3635import adminSeoRoutes from './routes/admin-seo.js';
    3736import audioRoutes from './routes/audio.js';
    3837import searchRoutes from './routes/search.js';
    39 import commentsRoutes from './routes/comments.js';
    4038import tagsRoutes from './routes/tags.js';
    4139import typesRoutes from './routes/types.js';
     
    305303app.use('/admin/sites', adminSitesRoutes);
    306304app.use('/admin/users', adminUsersRoutes);
    307 app.use('/admin/comments', adminCommentsRoutes);
    308305app.use('/admin/settings', adminSettingsRoutes);
    309306app.use('/admin/seo', adminSeoRoutes);
     
    318315if (audioEnabled()) app.use('/audio', audioRoutes);
    319316app.use('/search', searchRoutes);
    320 app.use('/comments', commentsRoutes);
    321317app.use('/tag', tagsRoutes);
    322318app.use('/type', typesRoutes);
  • src/views/pages/admin-site-edit.ejs

    r2e9773f r59f0170  
    147147        <input type="checkbox" name="robots_index" value="1" <%= site.robots_index ? 'checked' : '' %>>
    148148        <span><%= t('asite.robots_index') %></span>
    149       </label>
    150       <label class="cb">
    151         <input type="checkbox" name="require_login_to_comment" value="1" <%= site.require_login_to_comment ? 'checked' : '' %>>
    152         <span><%= t('asite.require_login_comment') %></span>
    153       </label>
    154       <label>
    155         <span><%= t('asite.comment_moderation') %></span>
    156         <select name="comments_moderation_mode">
    157           <option value="trust"    <%= (site.comments_moderation_mode || 'trust') === 'trust'    ? 'selected' : '' %>><%= t('asite.moderation_trust') %></option>
    158           <option value="moderate" <%= site.comments_moderation_mode === 'moderate' ? 'selected' : '' %>><%= t('asite.moderation_moderate') %></option>
    159         </select>
    160149      </label>
    161150      <% if (typeof audioEnabled === 'undefined' || audioEnabled) { %>
  • src/views/pages/admin.ejs

    r2e9773f r59f0170  
    3131      <a href="/admin/playlists" class="btn"><%= t('admin.b_playlists') %></a>
    3232      <% } %>
    33       <a href="/admin/comments" class="btn"><%= t('admin.b_comments') %></a>
    3433      <% if (primarySite) { %><a href="/admin/seo" class="btn"><%= t('admin.b_seo') %></a><% } %>
    3534      <a href="/admin/settings" class="btn"><%= t('admin.b_settings') %></a>
     
    3938      <a href="/admin/audio" class="btn"><%= t('admin.b_audio') %></a>
    4039      <% } %>
    41       <a href="/admin/comments" class="btn"><%= t('admin.b_comments') %></a>
    4240      <% if (primarySite) { %>
    4341        <a href="/admin/sites/<%= primarySite.slug %>/edit" class="btn"><%= t('admin.b_look') %></a>
  • src/views/pages/my-site.ejs

    r2e9773f r59f0170  
    77    <a href="/user/<%= mySite.slug %>/posts/new" class="btn">✍️ <%= t('myst.new_post') %></a>
    88    <a href="/admin/sites/<%= mySite.slug %>/edit" class="btn">🎨 <%= t('myst.appearance') %></a>
    9     <a href="/user/<%= mySite.slug %>/admin/comments" class="btn">💬 <%= t('myst.comments') %></a>
    109    <a href="/user/<%= mySite.slug %>" class="btn">👁️ <%= t('myst.view_site') %></a>
    1110    <a href="/account" class="btn">👤 <%= t('myst.account') %></a>
  • src/views/pages/post.ejs

    r2e9773f r59f0170  
    112112  </script>
    113113
    114   <!-- Comments -->
    115   <section class="post-comments" id="comments">
    116     <h2 class="comments-heading">
    117       <%= t(totalComments === 1 ? 'comments.heading_one' : 'comments.heading_other', { n: totalComments }) %>
    118     </h2>
    119 
    120     <% if (typeof currentPath === 'string' && currentPath && currentPath.indexOf('/' + post.slug) === 0) { %>
    121       <% /* If the URL has ?pending=1, the previous submit landed in the moderation queue */ %>
    122     <% } %>
    123     <script>
    124       (function() {
    125         if (location.search.indexOf('pending=1') !== -1) {
    126           var s = document.createElement('div');
    127           s.className = 'comments-pending-flash';
    128           s.textContent = <%- JSON.stringify(t('comments.pending')) %>;
    129           document.currentScript.parentNode.insertBefore(s, document.currentScript);
    130         }
    131       })();
    132     </script>
    133 
    134     <% if (!comments || !comments.length) { %>
    135       <p class="comments-empty"><%= t('comments.empty') %><% if (!user) { %> <a href="/auth/login?next=<%= encodeURIComponent(_base + '/' + post.slug + '#comments') %>"><%= t('nav.login') %></a> <%= t('comments.to_start') %><% } %></p>
    136     <% } else { %>
    137       <ol class="comments-list">
    138         <% comments.forEach(function(c) { %>
    139           <li class="comment" id="comment-<%= c.id %>">
    140             <div class="comment-avatar">
    141               <% if (c.author_avatar) { %>
    142                 <img src="<%= c.author_avatar %>" alt="">
    143               <% } else { %>
    144                 <span><%= c.author_username.charAt(0).toUpperCase() %></span>
    145               <% } %>
    146             </div>
    147             <div class="comment-body">
    148               <div class="comment-meta">
    149                 <a class="comment-author" href="/users/<%= encodeURIComponent(c.author_username) %>"><%= c.author_username %></a>
    150                 <span class="comment-time" title="<%= c.created_at %>"><%= formatDateTime(c.created_at) %></span>
    151               </div>
    152               <div class="comment-content"><%= c.content %></div>
    153               <div class="comment-actions">
    154                 <% if (user && canMutate) { %>
    155                   <button type="button" class="comment-reply-btn" data-reply-to="<%= c.id %>"><%= t('comments.reply') %></button>
    156                 <% } %>
    157                 <% if (user && permissions.canDeleteComment(user, c, site)) { %>
    158                   <form method="post" action="<%= _base %>/comments/<%= c.id %>/delete" onsubmit="return confirm('<%= t('comments.delete_confirm') %>')">
    159                     <button type="submit" class="comment-delete-btn"><%= t('comments.delete') %></button>
    160                   </form>
    161                 <% } %>
    162               </div>
    163 
    164               <!-- Inline reply form (hidden by default) -->
    165               <% if (user && canMutate) { %>
    166                 <form method="post" action="<%= _base %>/comments" class="comment-reply-form" hidden data-reply-form-for="<%= c.id %>">
    167                   <input type="hidden" name="post_slug" value="<%= post.slug %>">
    168                   <input type="hidden" name="parent_comment_id" value="<%= c.id %>">
    169                   <textarea name="content" rows="3" maxlength="4000" placeholder="<%= t('comments.reply_to', { name: c.author_username }) %>" required></textarea>
    170                   <div class="comment-reply-form-actions">
    171                     <button type="submit" class="btn btn-primary"><%= t('comments.reply') %></button>
    172                     <button type="button" class="btn comment-cancel-reply"><%= t('comments.cancel') %></button>
    173                   </div>
    174                 </form>
    175               <% } %>
    176 
    177               <!-- Replies (1 level deep) -->
    178               <% if (c.replies && c.replies.length) { %>
    179                 <ol class="comment-replies">
    180                   <% c.replies.forEach(function(r) { %>
    181                     <li class="comment comment-reply" id="comment-<%= r.id %>">
    182                       <div class="comment-avatar">
    183                         <% if (r.author_avatar) { %>
    184                           <img src="<%= r.author_avatar %>" alt="">
    185                         <% } else { %>
    186                           <span><%= r.author_username.charAt(0).toUpperCase() %></span>
    187                         <% } %>
    188                       </div>
    189                       <div class="comment-body">
    190                         <div class="comment-meta">
    191                           <a class="comment-author" href="/users/<%= encodeURIComponent(r.author_username) %>"><%= r.author_username %></a>
    192                           <span class="comment-time"><%= formatDateTime(r.created_at) %></span>
    193                         </div>
    194                         <div class="comment-content"><%= r.content %></div>
    195                         <div class="comment-actions">
    196                           <% if (user && canMutate) { %>
    197                             <button type="button" class="comment-reply-btn" data-reply-to="<%= r.id %>"><%= t('comments.reply') %></button>
    198                           <% } %>
    199                           <% if (user && permissions.canDeleteComment(user, r, site)) { %>
    200                             <form method="post" action="<%= _base %>/comments/<%= r.id %>/delete" onsubmit="return confirm('<%= t('comments.delete_confirm') %>')">
    201                               <button type="submit" class="comment-delete-btn"><%= t('comments.delete') %></button>
    202                             </form>
    203                           <% } %>
    204                         </div>
    205 
    206                         <%# Replying to a reply — lands (via resolvedParent on the
    207                             server) in the same thread, so the 1-level depth is preserved. %>
    208                         <% if (user && canMutate) { %>
    209                           <form method="post" action="<%= _base %>/comments" class="comment-reply-form" hidden data-reply-form-for="<%= r.id %>">
    210                             <input type="hidden" name="post_slug" value="<%= post.slug %>">
    211                             <input type="hidden" name="parent_comment_id" value="<%= r.id %>">
    212                             <textarea name="content" rows="3" maxlength="4000" placeholder="<%= t('comments.reply_to', { name: r.author_username }) %>" required></textarea>
    213                             <div class="comment-reply-form-actions">
    214                               <button type="submit" class="btn btn-primary"><%= t('comments.reply') %></button>
    215                               <button type="button" class="btn comment-cancel-reply"><%= t('comments.cancel') %></button>
    216                             </div>
    217                           </form>
    218                         <% } %>
    219                       </div>
    220                     </li>
    221                   <% }); %>
    222                 </ol>
    223               <% } %>
    224             </div>
    225           </li>
    226         <% }); %>
    227       </ol>
    228     <% } %>
    229 
    230     <!-- Top-level comment form (only for logged-in users for now) -->
    231     <% if (user && canMutate) { %>
    232       <form method="post" action="<%= _base %>/comments" class="comment-form">
    233         <input type="hidden" name="post_slug" value="<%= post.slug %>">
    234         <label class="comment-form-label">
    235           <span><%= t('comments.add_as') %> <strong><%= user.username %></strong></span>
    236           <textarea name="content" rows="3" maxlength="4000" placeholder="<%= t('comments.placeholder') %>" required></textarea>
    237         </label>
    238         <button type="submit" class="btn btn-primary"><%= t('comments.post') %></button>
    239       </form>
    240     <% } else if (!user) { %>
    241       <p class="comments-login-cta">
    242         <a href="/auth/login?next=<%= encodeURIComponent(_base + '/' + post.slug + '#comments') %>" class="btn"><%= t('comments.login_to_comment') %></a>
    243       </p>
    244     <% } else { %>
    245       <p class="comments-login-cta" style="color:var(--ink-muted)">👁️ Kijker-modus — reageren is uitgeschakeld.</p>
    246     <% } %>
    247   </section>
    248 
    249   <!-- Reply form toggle -->
    250   <script>
    251   (function() {
    252     document.querySelectorAll('.comment-reply-btn').forEach(function(btn) {
    253       btn.addEventListener('click', function() {
    254         var id = btn.dataset.replyTo;
    255         var form = document.querySelector('[data-reply-form-for="' + id + '"]');
    256         if (form) {
    257           form.hidden = !form.hidden;
    258           if (!form.hidden) form.querySelector('textarea').focus();
    259         }
    260       });
    261     });
    262     document.querySelectorAll('.comment-cancel-reply').forEach(function(btn) {
    263       btn.addEventListener('click', function() {
    264         btn.closest('.comment-reply-form').hidden = true;
    265       });
    266     });
    267   })();
    268   </script>
    269114
    270115  <%# ── Related posts (3-card grid) ─────────────────────────────
Note: See TracChangeset for help on using the changeset viewer.