Lyrasoft Feedback package, contains comments and rating functions.
Install from composer
composer require lyrasoft/feedbackThen copy files to project
php windwalker pkg:install lyrasoft/feedback -t routes -t migrations -t seedersAdd this line to admin & front middleware if you don't want to override languages:
$this->lang->loadAllFromVendor('lyrasoft/feedback', 'ini');
// OR
$this->lang->loadAllFromVendor(\Lyrasoft\Feedback\FeedbackPackage::class, 'ini');Or run this command to copy languages files:
php windwalker pkg:install lyrasoft/feedback -t lang
There are 2 example seeders auto installed, add comment.seeder.php and rating.seeder.php to
resources/seeders/main.php
return [
// ...
__DIR__ . '/comment.seeder.php',
__DIR__ . '/rating.seeder.php',
// ...
];If you don't need example seeders, write your own seeder by adding comments through service:
foreach ($articleIds as $articleId) {
foreach (range(1, random_int(2, 5)) as $i) {
$userId = $faker->randomElement($userIds);
$item = $commentService->addComment(
$type,
$articleId,
$faker->paragraph(4),
$userId,
extra: function (Comment $item) use ($faker) {
$item->title = $faker->sentence(2);
$item->created = $faker->dateTimeThisYear();
$item->ordering = $item->count() + 1;
}
);
}
}Edit resources/menu/admin/sidemenu.menu.php
You must add type to route, every comment should contains type.
// Comment: Article
$menu->link('評論管理: 文章')
->to($nav->to('comment_list')->var('type', 'article'))
->icon('fal fa-comments');
// Or use translations
$menu->link($lang('feedback.comment.list.title', title: $lang('luna.article.title')))
->to($nav->to('comment_list')->var('type', 'article'))
->icon('fal fa-comments');Add a comment to a type:
/** @var \Lyrasoft\Feedback\Service\CommentService $commentService */
$commentService->addComment(
'flower', // Type
$targetId, // Target ID
'Comment Text...', // Content
$user->id, // User ID
);Add a comment and configure Comment entity:
use Lyrasoft\Feedback\Entity\Comment;
/** @var \Lyrasoft\Feedback\Service\CommentService $commentService */
$commentService->addComment(
'flower', // Type
$targetId, // Target ID
'Comment Text...', // Content
$user->id, // User ID
// The extra can be callback or array
extra: function (Comment $comment) {
$comment->rating = 5; // If user mark as 5 star
$comment->nickname = 'Another nickname';
}
);Comments ordering:
/** @var \Lyrasoft\Feedback\Service\CommentService $commentService */
$newComment = $commentService->addComment(
'flower', // Type
$targetId, // Target ID
'Comment Text...', // Content
$user->id, // User ID or User entity
extra: function (Comment $comment) use ($commentService) {
$count = $commentService->countWith($comment);
// OR
$count = $comment->count();
// This optional if you want to set ordering to one comment
$comment->ordering = $count + 1;
}
);
// Or reorder all comments of one target item.
$commentService->reorderComments(
'flower', // Type
$targetId, // Target ID
);
// OR
$commentService->reorderWith($newComment);There are 2 ways to add reply, one is just write reply content to comment, every comment contains only 1 reply:
/** @var \Lyrasoft\Feedback\Service\CommentService $commentService */
$commentService->addInstantReply(
$comment, // Can be ID or entity
'Reply text...',
$user->id, // User ID or User entity
);The other way is to create sub comments:
/** @var \Lyrasoft\Feedback\Service\CommentService $commentService */
$childComment = $commentService->addSubReply(
$parentComment, // Can be ID or entity
'Reply text...',
$user->id, // User ID or User entity
extra: function (Comment $comment) {
// Configure comment entity before save
}
);
// Optional: if you want to reorder it.
$commentService->reorderComments(
$parentComment->type, // Type
$parentComment->targetId, // Target ID
$parentComment->id, // Parent ID
);
// OR
$commentService->reorderWith($childComment);If you want to add comment with ratings, you can simply add rating number to comment entity, and then update the origin rated item with new average rating:
/** @var \Lyrasoft\Feedback\Service\CommentService $commentService */
$newComment = $commentService->addComment(
'flower', // Type
$targetId, // Target ID
'Comment Text...', // Content
$user->id, // User ID or User entity
extra: function (Comment $comment) use ($commentService) {
$comment->rating = 4; // If user mark as 4 star
}
);
// Update origin rated item
$orm->updateBatch(
Target::class,
[
'rating_avg' => $commentService->calcAvgRatingWith($newComment),
'rating_count' => $commentService->countWith($newComment),
],
$targetId,
);![note] If you need a like system without comments, you can use
Ratingentity withoutCommententity, and simply usecount()to count total likes. See Rating
/** @var \Lyrasoft\Feedback\Service\CommentService $commentService */
// Create Comment Item
$comment = $commentService->createCommentItem($type, $targetId, 'text...', $user);
// count Comments
$count = $commentService->countComments($type, $targetId);
// Reorder
$commentService->reorderComments($type, $targetId);
// Calc average rating
$avg = $commentService->calcAvgRating($type, $targetId);
$avg = $commentService->calcAvgRatingWith($comment);
// Check has commented
$comment = $commentService->getComment($type, $targetId);
$bool = $commentService->isCommented($type, $targetId);
// Remove comment
$commentService->removeComment($type, $targetId);You can use Rating for the following purposes:
- Like system: Users can like content to show their supports. In this case, the Rating's
rankfield can always be set to1as one LIKE. Afterwards, simply usecount()to count the total likes. - Star rating system: Users can rate content with stars, e.g., from 1 to 5. In this case, the Rating's
rankfield can be set to the numeric star chosen by the user, and you can usecalcAvgRank()to calculate the average.
Note
If you want users to leave comments while rating, it's recommended to use the comments table with a rating
column, so you don't need a separate ratings table. See Comment With Ratings
Add a rating to a type:
/** @var \Lyrasoft\Feedback\Service\RatingService $ratingService */
$ratingService->addRating(
'flower', // Type
$targetId, // Target ID
$user->id, // User ID
);Add rating if not rated, and configure Rating entity:
use Lyrasoft\Feedback\Entity\Rating;
/** @var \Lyrasoft\Feedback\Service\RatingService $ratingService */
$ratingService->addRatingIfNotRated(
'flower', // Type
$targetId, // Target ID
$user->id, // User ID
// The extra can be callback or array
extra: function (Rating $rating) {
$rating->rank = 4.5; // If user mark as 4.5 star
}
);Rating ordering:
/** @var \Lyrasoft\Feedback\Service\RatingService $ratingService */
$newRating = $ratingService->addRating(
'flower', // Type
$targetId, // Target ID
$user->id, // User ID or User entity
extra: function (Rating $rating) use ($ratingService, $targetId) {
$count = $ratingService->countWith($rating);
// OR
$count = $rating->count();
// This optional if you want to set ordering to one comment
$rating->ordering = $count + 1;
}
);
// Update origin rated item
$orm->updateBatch(
Target::class,
['rating' => $ratingService->calcAvgWith($rating)],
$targetId,
);
// Or reorder all ratings of one target item.
$ratingService->reorderRatings(
'flower', // Type
$targetId, // Target ID
);
// OR
$ratingService->reorderWith($newRating);/** @var \Lyrasoft\Feedback\Service\RatingService $ratingService */
// Calc average rank
$avg = $ratingService->calcAvgRank($type, $targetId);
$avg = $ratingService->calcAvgWith($rating);
// Get rating item or check is rated
$item = $ratingService->getRating($type, $targetId);
$bool = $ratingService->isRated($type, $targetId);
// Remove
$ratingService->removeRating($type, $targetId);Add useRatingButtons() to front main.ts file:
import { useRatingButtons } from '@lyrasoft/feedback';
// ...
useRatingButtons();Then uou can add button components everywhere in blade template:
<div class="card c-item-card">
<x-rating-button
type="item"
:id="$item->id"
:rated="$item->rated"
class="..."
></x-rating-button>
<div class="card-body">
...
</div>
</div>
Available params:
| Name | Type | Description |
|---|---|---|
type |
string or enum | The rating type |
id |
string or int | The item ID |
rated |
bool or int | Is this item rated by current user, will auto load from DB if without this params. |
class-active |
string | The button class if active. |
class-inactive |
string | The button class if inactive. |
icon-active |
string | The icon class if active. |
icon-inactive |
string | The button class if inactive. |
title-active |
string | The tooltip title if active. |
title-inactive |
string | The tooltip title if inactive. |
tag |
string | The button HTML tag. |
By default, favorite package will not allow any types sent from browser.
You can configure allowed types in config file:
return [
'feedback' => [
// ...
'rating' => [
'ajax_type_protect' => true,
'ajax_allow_types' => [
'article',
'...' // <-- Add your new types here
]
],
]
];You can also set the ajax_type_protect to FALSE but we don't recommend to do this.
You can listen events after rated actions:
// Select all favorite buttons, you can use your own class to select it.
const buttons = document.querySelectorAll('[uni-rating-button]');
for (const button of buttons) {
button.addEventListener('rated', (e) => {
u.notify(e.detail.message, 'success');
// Available details
e.detail.rated;
e.detail.type;
e.detail.task;
e.detail.message;
});
}Or listen globally:
import { simpleNotify } from '@windwalker-io/unicorn-next';
document.addEventListener('rated', (e) => {
if (e.detail.type === 'comment') {
if (e.detail.favorited) {
simpleNotify('已按讚', 'success');
} else {
simpleNotify('已收回讚', 'success');
}
}
});Use uni-rating-button directive to auto enable button in Vue app.
<a href="javascript://"
uni-favorite-button
:data-rated="rated"
:data-type="type"
data-class-active=""
data-class-inactive=""
data-icon-active="fas fa-heart"
data-icon-inactive="far fa-heart"
data-title-active="..."
data-title-inactive="..."
>
<i></i>
</a>use Lyrasoft\Feedback\Repository\RatingRepository;
// In any repository
public function getFrontListSelector(?User $user = null): ListSelector
{
$selector = $this->getListSelector();
if ($user && $user->isLogin()) {
RatingRepository::joinRating(
$selector,
'item',
$user->id,
'item.id'
);
}
// ...In blade:
@foreach ($items of $item)
<div>
...
<x-rating-button
type="item"
:id="$item->id"
:rated="$item->rated"
class="..."
></x-rating-button>
...
</div>
@endforeach