Skip to content

Threaded (nested) comments with a collapsible, connector-line UI#103

Open
ItsMalikJones wants to merge 2 commits into
mainfrom
feat/11-comment-threading
Open

Threaded (nested) comments with a collapsible, connector-line UI#103
ItsMalikJones wants to merge 2 commits into
mainfrom
feat/11-comment-threading

Conversation

@ItsMalikJones
Copy link
Copy Markdown

Closes #11

Summary

Adds opt-in threaded comment replies to Commentions. Users can reply directly
to a comment, replies render recursively beneath their parent, and long
sub-threads can be collapsed. The feature is disabled by default and gated behind
a new config block, so existing installations are unaffected until they opt in.

What's included

Data layer

  • Additive migration — a new nullable, self-referencing parent_id column on
    the comments table (add_parent_id_to_commentions_comments_table.php.stub),
    with a foreign key that cascades on delete.
  • Comment modelparent() / replies() relations, a depth() helper
    that walks the parent chain, and repliesCount() which recursively counts all
    descendants.
  • HasComments::commentsQuery() — returns only top-level comments when
    threading is enabled, and eager-loads replies down to the configured
    max_depth to avoid N+1 queries.
  • SaveComment — accepts an optional parentId so replies persist with the
    correct parent.
  • Deleting a comment recursively deletes its replies.

UI

  • A Reply control on each comment opens an inline editor; saveReply()
    persists the reply via SaveComment. The control is hidden once a comment
    reaches max_depth, both in the UI and enforced server-side.
  • Replies render recursively, each indented beneath its parent.
  • Collapsible sub-threads — every comment with replies gets an accessible
    toggle (<button> with aria-expanded / aria-controls) that collapses or
    expands its sub-thread and shows a descendant count ("3 replies") when
    collapsed. Collapsing is handled client-side via Alpine, so no extra server
    round-trips and nested component state is preserved.
  • Facebook-style connector lines — replies are joined to their parent by a
    curved elbow connector (a continuous vertical trunk with a rounded branch into
    each reply's avatar). The trunk ends cleanly at the last reply. Drawn with pure
    CSS, including dark-mode colours.
  • Capped indentation — horizontal indent stops being added past depth 2 so
    deep threads stay readable on small screens; the connector line still renders
    at every level.
  • New English strings (replies_count, hide_replies) plus a reply key added
    across all bundled locales (ar, en, es, fr, nl, ro).

Configuration

A new commentions.threading config block:

'threading' => [
    'enabled'   => env('COMMENTIONS_THREADING_ENABLED', false),
    'max_depth' => (int) env('COMMENTIONS_THREADING_MAX_DEPTH', 3),
],

Threading is disabled by default. max_depth is the deepest reply level
allowed — top-level comments are depth 0, so the default of 3 permits replies
down to depth 3.

Backward compatibility

  • Non-breaking. With threading.enabled = false (the default), behaviour is
    identical to today — a flat comment list.
  • The migration is purely additive (new nullable column); existing comments
    become top-level comments (parent_id = null).
  • No changes to existing public method signatures (SaveComment's new argument
    is optional).

Testing

  • 16 feature tests in tests/Livewire/CommentThreadingTest.php cover: creating
    replies, depth calculation, top-level-only querying, reply-form behaviour,
    max_depth enforcement (UI + server), cascade deletion, repliesCount(),
    card-vs-flat rendering by depth, the collapse toggle markup and ARIA
    attributes, the reply-count label, connector rendering, and the indentation
    cap.

Adds threaded comment replies.

- New nullable self-referencing `parent_id` column (additive migration);
  the Comment model gains parent()/replies() relations and a depth() helper.
- HasComments::commentsQuery() returns only top-level comments when threading
  is enabled and eager-loads replies down to the configured max depth.
- A "Reply" Filament action on each comment opens an inline reply editor;
  saveReply() persists the reply with its parent_id via SaveComment. The
  reply control is hidden once a comment reaches the max depth.
- Replies render recursively beneath their parent with an indent.
- Controlled by a `commentions.threading` config block (disabled by default,
  default max_depth 3).
- Deleting a comment recursively deletes its replies (and their files).
- Add Facebook-style thread connector lines for nested replies
- Implement collapsible replies with accessible toggle showing
  descendant count
- Cap horizontal indentation at depth 2 to prevent layout issues on
  small screens
- Add `repliesCount()` method to recursively count all descendant
  comments
- Update CSS for thread lines with dark mode support
- Add English translations for replies UI
@ItsMalikJones ItsMalikJones self-assigned this May 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add ability to nest comments

1 participant