Threaded (nested) comments with a collapsible, connector-line UI - #103
Threaded (nested) comments with a collapsible, connector-line UI#103ItsMalikJones wants to merge 3 commits into
Conversation
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
lisa-fehr
left a comment
There was a problem hiding this comment.
(nit) may be ignored or added later - but other comments will need to be addressed.
| 'replies_count' => '{0} No replies|{1} :count reply|[2,*] :count replies', | ||
| 'hide_replies' => 'Hide replies', |
There was a problem hiding this comment.
Result
Missing replies_count and hide_replies in other language files. It is using English for the other languages.
Expectation
Add the missing translations
| @@ -0,0 +1,28 @@ | |||
| <?php | |||
There was a problem hiding this comment.
Result:
Missing README instructions for adding comment threads
Expectation:
-
Add a README section that explains how to implement the comment threads IE: Set
COMMENTIONS_THREADING_ENABLED=true. -
(nit) Add a generic Upgrading README section. IE: Upgrading > Rerun:
php artisan vendor:publish --tag="commentions-migrations"thenphp artisan migrate. Or register an install command to rerun setup.
| .mention-item:hover { | ||
| @apply comm:bg-gray-100; | ||
| } | ||
|
|
There was a problem hiding this comment.
(nit)
Result:
I didn't see the new styles when enabling this on an existing package install.
Expectation
- Add generic Upgrade instructions to the README. IE: Run:
php artisan vendor:publish --tag=filament-assets --force(or similar) thenphp artisan optimize:clear. - Add to install instructions IE: Run:
php artisan filament:assetsor add an install command
| $paths = []; | ||
| $prefix = 'replies'; | ||
|
|
||
| for ($level = 1; $level <= $maxDepth; $level++) { |
There was a problem hiding this comment.
(nit)
Result
-
No eager loading added to the query if depth is
0andCOMMENTIONS_THREADING_ENABLED=true(unexpected scenario)- When
COMMENTIONS_THREADING_MAX_DEPTH=0 - Then
$paths=[] - When
Model::preventLazyLoading();is used - Then it throws a lazy loading warning
- When
-
Does not eager load all children when env depth has been reduced after comments were saved.
Expectation
Add a note with the README instructions to set depth to 1+ and not to reduce depth once there are comments.
| x-bind:class="expanded ? '' : 'comm:-rotate-90'" | ||
| /> | ||
| <span x-show="expanded">{{ __('commentions::comments.hide_replies') }}</span> | ||
| <span x-show="!expanded" x-cloak>{{ trans_choice('commentions::comments.replies_count', $comment->repliesCount(), ['count' => $comment->repliesCount()]) }}</span> |
There was a problem hiding this comment.
(nit) repliesCount() is called 4 times. Could improve this by saving it to a field.
| expect($reply->replies()->count())->toBe(0); | ||
| }); | ||
|
|
||
| test('deleting a comment deletes its nested replies', function () { |
There was a problem hiding this comment.
(nit) Best practice is to continue showing the replies and leave a 'deleted' stub for the removed parent comment.
| * the thread line still renders, but no extra padding is added so deep | ||
| * threads don't compound horizontally on small screens. | ||
| */ | ||
| public const INDENT_CAP_DEPTH = 2; |
There was a problem hiding this comment.
Result
I added COMMENTIONS_THREADING_MAX_DEPTH=10 and it continued to indent a large amount from the left after '2'. In addition, it pushed the comments out of the available space when using a modal.
The style came from a different place in comment.blade.php@line 8: comm:pl-6.
'comm:relative comm:gap-x-3 comm:py-2 comm:pl-6' => $depth > 0Expectation
Correct the line of code above to follow the same rule as shouldIndentReplies() which uses COMMENTIONS_THREADING_MAX_DEPTH.
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
parent_idcolumn onthe comments table (
add_parent_id_to_commentions_comments_table.php.stub),with a foreign key that cascades on delete.
Commentmodel —parent()/replies()relations, adepth()helperthat walks the parent chain, and
repliesCount()which recursively counts alldescendants.
HasComments::commentsQuery()— returns only top-level comments whenthreading is enabled, and eager-loads replies down to the configured
max_depthto avoid N+1 queries.SaveComment— accepts an optionalparentIdso replies persist with thecorrect parent.
UI
saveReply()persists the reply via
SaveComment. The control is hidden once a commentreaches
max_depth, both in the UI and enforced server-side.toggle (
<button>witharia-expanded/aria-controls) that collapses orexpands 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.
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.
deep threads stay readable on small screens; the connector line still renders
at every level.
replies_count,hide_replies) plus areplykey addedacross all bundled locales (
ar,en,es,fr,nl,ro).Configuration
A new
commentions.threadingconfig block:Threading is disabled by default.
max_depthis the deepest reply levelallowed — top-level comments are depth 0, so the default of
3permits repliesdown to depth 3.
Backward compatibility
threading.enabled = false(the default), behaviour isidentical to today — a flat comment list.
become top-level comments (
parent_id = null).SaveComment's new argumentis optional).
Testing
tests/Livewire/CommentThreadingTest.phpcover: creatingreplies, depth calculation, top-level-only querying, reply-form behaviour,
max_depthenforcement (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.