mirror of
https://code.castopod.org/adaures/castopod
synced 2025-04-19 13:01:19 +00:00
feat(comments): add like / undo like to comment + add comment page
This commit is contained in:
parent
bb4752c35e
commit
0c187ef7a9
@ -771,12 +771,24 @@ $routes->group('@(:podcastHandle)', function ($routes): void {
|
||||
'controller-method' => 'EpisodeController::comments/$1/$2',
|
||||
],
|
||||
]);
|
||||
$routes->get('comments/(:uuid)', 'EpisodeController::comment/$1/$2/$3', [
|
||||
$routes->get('comments/(:uuid)', 'EpisodeCommentController::view/$1/$2/$3', [
|
||||
'as' => 'comment',
|
||||
'application/activity+json' => [
|
||||
'controller-method' => 'EpisodeController::commentObject/$1/$2',
|
||||
],
|
||||
'application/podcast-activity+json' => [
|
||||
'controller-method' => 'EpisodeController::commentObject/$1/$2',
|
||||
],
|
||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
||||
'controller-method' => 'EpisodeController::commentObject/$1/$2',
|
||||
],
|
||||
]);
|
||||
$routes->get('comments/(:uuid)/replies', 'EpisodeController::commentReplies/$1/$2/$3', [
|
||||
$routes->get('comments/(:uuid)/replies', 'EpisodeCommentController::replies/$1/$2/$3', [
|
||||
'as' => 'comment-replies',
|
||||
]);
|
||||
$routes->post('comments/(:uuid)/like', 'EpisodeCommentController::attemptLike/$1/$2/$3', [
|
||||
'as' => 'comment-attempt-like',
|
||||
]);
|
||||
$routes->get('oembed.json', 'EpisodeController::oembedJSON/$1/$2', [
|
||||
'as' => 'episode-oembed-json',
|
||||
]);
|
||||
|
@ -10,13 +10,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Entities\Comment;
|
||||
use App\Entities\Episode;
|
||||
use App\Entities\EpisodeComment;
|
||||
use App\Entities\Image;
|
||||
use App\Entities\Location;
|
||||
use App\Entities\Podcast;
|
||||
use App\Entities\Post;
|
||||
use App\Models\CommentModel;
|
||||
use App\Models\EpisodeCommentModel;
|
||||
use App\Models\EpisodeModel;
|
||||
use App\Models\PodcastModel;
|
||||
use App\Models\PostModel;
|
||||
@ -800,7 +800,7 @@ class EpisodeController extends BaseController
|
||||
|
||||
$message = $this->request->getPost('message');
|
||||
|
||||
$newComment = new Comment([
|
||||
$newComment = new EpisodeComment([
|
||||
'actor_id' => interact_as_actor_id(),
|
||||
'episode_id' => $this->episode->id,
|
||||
'message' => $message,
|
||||
@ -808,7 +808,7 @@ class EpisodeController extends BaseController
|
||||
'created_by' => user_id(),
|
||||
]);
|
||||
|
||||
$commentModel = new CommentModel();
|
||||
$commentModel = new EpisodeCommentModel();
|
||||
if (
|
||||
! $commentModel->addComment($newComment, true)
|
||||
) {
|
||||
|
173
app/Controllers/EpisodeCommentController.php
Normal file
173
app/Controllers/EpisodeCommentController.php
Normal file
@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use ActivityPub\Entities\Actor;
|
||||
use ActivityPub\Objects\OrderedCollectionObject;
|
||||
use ActivityPub\Objects\OrderedCollectionPage;
|
||||
use Analytics\AnalyticsTrait;
|
||||
use App\Controllers\Admin\BaseController;
|
||||
use App\Entities\Episode;
|
||||
use App\Entities\EpisodeComment;
|
||||
use App\Entities\Podcast;
|
||||
use App\Libraries\CommentObject;
|
||||
use App\Models\EpisodeCommentModel;
|
||||
use App\Models\EpisodeModel;
|
||||
use App\Models\PodcastModel;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\Response;
|
||||
|
||||
class EpisodeCommentController extends BaseController
|
||||
{
|
||||
use AnalyticsTrait;
|
||||
|
||||
protected Podcast $podcast;
|
||||
|
||||
protected Actor $actor;
|
||||
|
||||
protected Episode $episode;
|
||||
|
||||
protected EpisodeComment $comment;
|
||||
|
||||
public function _remap(string $method, string ...$params): mixed
|
||||
{
|
||||
if (count($params) < 3) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
if (
|
||||
($podcast = (new PodcastModel())->getPodcastByHandle($params[0])) === null
|
||||
) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$this->podcast = $podcast;
|
||||
$this->actor = $podcast->actor;
|
||||
|
||||
if (
|
||||
($episode = (new EpisodeModel())->getEpisodeBySlug($params[0], $params[1])) === null
|
||||
) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$this->episode = $episode;
|
||||
|
||||
if (
|
||||
($comment = (new EpisodeCommentModel())->getCommentById($params[2])) === null
|
||||
) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$this->comment = $comment;
|
||||
|
||||
unset($params[2]);
|
||||
unset($params[1]);
|
||||
unset($params[0]);
|
||||
|
||||
return $this->{$method}(...$params);
|
||||
}
|
||||
|
||||
public function view(): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
$this->registerPodcastWebpageHit($this->podcast->id);
|
||||
}
|
||||
|
||||
$cacheName = implode(
|
||||
'_',
|
||||
array_filter([
|
||||
'page',
|
||||
"comment#{$this->comment->id}",
|
||||
service('request')
|
||||
->getLocale(),
|
||||
can_user_interact() ? '_authenticated' : null,
|
||||
]),
|
||||
);
|
||||
|
||||
if (! ($cachedView = cache($cacheName))) {
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'actor' => $this->actor,
|
||||
'episode' => $this->episode,
|
||||
'comment' => $this->comment,
|
||||
];
|
||||
|
||||
// if user is logged in then send to the authenticated activity view
|
||||
if (can_user_interact()) {
|
||||
helper('form');
|
||||
return view('podcast/comment_authenticated', $data);
|
||||
}
|
||||
return view('podcast/comment', $data, [
|
||||
'cache' => DECADE,
|
||||
'cache_name' => $cacheName,
|
||||
]);
|
||||
}
|
||||
|
||||
return $cachedView;
|
||||
}
|
||||
|
||||
/**
|
||||
* @noRector ReturnTypeDeclarationRector
|
||||
*/
|
||||
public function commentObject(): Response
|
||||
{
|
||||
$commentObject = new CommentObject($this->comment);
|
||||
|
||||
return $this->response
|
||||
->setContentType('application/json')
|
||||
->setBody($commentObject->toJSON());
|
||||
}
|
||||
|
||||
public function replies(): Response
|
||||
{
|
||||
/**
|
||||
* get comment replies
|
||||
*/
|
||||
$commentReplies = model('CommentModel', false)
|
||||
->where('in_reply_to_id', service('uuid')->fromString($this->comment->id)->getBytes())
|
||||
->orderBy('created_at', 'ASC');
|
||||
|
||||
$pageNumber = (int) $this->request->getGet('page');
|
||||
|
||||
if ($pageNumber < 1) {
|
||||
$commentReplies->paginate(12);
|
||||
$pager = $commentReplies->pager;
|
||||
$collection = new OrderedCollectionObject(null, $pager);
|
||||
} else {
|
||||
$paginatedReplies = $commentReplies->paginate(12, 'default', $pageNumber);
|
||||
$pager = $commentReplies->pager;
|
||||
|
||||
$orderedItems = [];
|
||||
if ($paginatedReplies !== null) {
|
||||
foreach ($paginatedReplies as $reply) {
|
||||
$replyObject = new CommentObject($reply);
|
||||
$orderedItems[] = $replyObject;
|
||||
}
|
||||
}
|
||||
|
||||
$collection = new OrderedCollectionPage($pager, $orderedItems);
|
||||
}
|
||||
|
||||
return $this->response
|
||||
->setContentType('application/activity+json')
|
||||
->setBody($collection->toJSON());
|
||||
}
|
||||
|
||||
public function attemptLike(): RedirectResponse
|
||||
{
|
||||
model('LikeModel')
|
||||
->toggleLike(interact_as_actor(), $this->comment);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
@ -15,10 +15,8 @@ use ActivityPub\Objects\OrderedCollectionPage;
|
||||
use Analytics\AnalyticsTrait;
|
||||
use App\Entities\Episode;
|
||||
use App\Entities\Podcast;
|
||||
use App\Libraries\CommentObject;
|
||||
use App\Libraries\NoteObject;
|
||||
use App\Libraries\PodcastEpisode;
|
||||
use App\Models\CommentModel;
|
||||
use App\Models\EpisodeModel;
|
||||
use App\Models\PodcastModel;
|
||||
use CodeIgniter\Database\BaseBuilder;
|
||||
@ -256,57 +254,4 @@ class EpisodeController extends BaseController
|
||||
->setHeader('Access-Control-Allow-Origin', '*')
|
||||
->setBody($collection->toJSON());
|
||||
}
|
||||
|
||||
/**
|
||||
* @noRector ReturnTypeDeclarationRector
|
||||
*/
|
||||
public function comment(string $commentId): Response
|
||||
{
|
||||
if (
|
||||
($comment = (new CommentModel())->getCommentById($commentId)) === null
|
||||
) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$commentObject = new CommentObject($comment);
|
||||
|
||||
return $this->response
|
||||
->setContentType('application/json')
|
||||
->setBody($commentObject->toJSON());
|
||||
}
|
||||
|
||||
public function commentReplies(string $commentId): Response
|
||||
{
|
||||
/**
|
||||
* get comment replies
|
||||
*/
|
||||
$commentReplies = model('CommentModel', false)
|
||||
->where('in_reply_to_id', service('uuid')->fromString($commentId)->getBytes())
|
||||
->orderBy('created_at', 'ASC');
|
||||
|
||||
$pageNumber = (int) $this->request->getGet('page');
|
||||
|
||||
if ($pageNumber < 1) {
|
||||
$commentReplies->paginate(12);
|
||||
$pager = $commentReplies->pager;
|
||||
$collection = new OrderedCollectionObject(null, $pager);
|
||||
} else {
|
||||
$paginatedReplies = $commentReplies->paginate(12, 'default', $pageNumber);
|
||||
$pager = $commentReplies->pager;
|
||||
|
||||
$orderedItems = [];
|
||||
if ($paginatedReplies !== null) {
|
||||
foreach ($paginatedReplies as $reply) {
|
||||
$replyObject = new CommentObject($reply);
|
||||
$orderedItems[] = $replyObject;
|
||||
}
|
||||
}
|
||||
|
||||
$collection = new OrderedCollectionPage($pager, $orderedItems);
|
||||
}
|
||||
|
||||
return $this->response
|
||||
->setContentType('application/activity+json')
|
||||
->setBody($collection->toJSON());
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,9 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddComments creates comments table in database
|
||||
* Class AddEpisodeComments creates episode_comments table in database
|
||||
*
|
||||
* @copyright 2020 Podlibre
|
||||
* @copyright 2021 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
@ -14,7 +14,7 @@ namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddComments extends Migration
|
||||
class AddEpisodeComments extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
@ -42,22 +42,16 @@ class AddComments extends Migration
|
||||
],
|
||||
'message' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 500,
|
||||
'null' => true,
|
||||
'constraint' => 5000,
|
||||
],
|
||||
'message_html' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 600,
|
||||
'null' => true,
|
||||
'constraint' => 6000,
|
||||
],
|
||||
'likes_count' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'dislikes_count' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'replies_count' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
@ -75,11 +69,11 @@ class AddComments extends Migration
|
||||
$this->forge->addForeignKey('episode_id', 'episodes', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('created_by', 'users', 'id');
|
||||
$this->forge->createTable('comments');
|
||||
$this->forge->createTable('episode_comments');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('comments');
|
||||
$this->forge->dropTable('episode_comments');
|
||||
}
|
||||
}
|
42
app/Database/Migrations/2021-08-12-160000_add_likes.php
Normal file
42
app/Database/Migrations/2021-08-12-160000_add_likes.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddLikes Creates likes table in database
|
||||
*
|
||||
* @copyright 2021 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace ActivityPub\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddLikes extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'actor_id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'comment_id' => [
|
||||
'type' => 'BINARY',
|
||||
'constraint' => 16,
|
||||
],
|
||||
]);
|
||||
$this->forge->addField('`created_at` timestamp NOT NULL DEFAULT current_timestamp()');
|
||||
$this->forge->addPrimaryKey(['actor_id', 'comment_id']);
|
||||
$this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('comment_id', 'episode_comments', 'id', '', 'CASCADE');
|
||||
$this->forge->createTable('likes');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('likes');
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ declare(strict_types=1);
|
||||
namespace App\Entities;
|
||||
|
||||
use App\Libraries\SimpleRSSElement;
|
||||
use App\Models\CommentModel;
|
||||
use App\Models\EpisodeCommentModel;
|
||||
use App\Models\PersonModel;
|
||||
use App\Models\PodcastModel;
|
||||
use App\Models\PostModel;
|
||||
@ -122,7 +122,7 @@ class Episode extends Entity
|
||||
protected ?array $posts = null;
|
||||
|
||||
/**
|
||||
* @var Comment[]|null
|
||||
* @var EpisodeComment[]|null
|
||||
*/
|
||||
protected ?array $comments = null;
|
||||
|
||||
@ -402,7 +402,7 @@ class Episode extends Entity
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Comment[]
|
||||
* @return EpisodeComment[]
|
||||
*/
|
||||
public function getComments(): array
|
||||
{
|
||||
@ -411,7 +411,7 @@ class Episode extends Entity
|
||||
}
|
||||
|
||||
if ($this->comments === null) {
|
||||
$this->comments = (new CommentModel())->getEpisodeComments($this->id);
|
||||
$this->comments = (new EpisodeCommentModel())->getEpisodeComments($this->id);
|
||||
}
|
||||
|
||||
return $this->comments;
|
||||
|
@ -10,6 +10,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Entities;
|
||||
|
||||
use App\Models\EpisodeCommentModel;
|
||||
use App\Models\EpisodeModel;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Michalsn\Uuid\UuidEntity;
|
||||
@ -23,22 +24,28 @@ use RuntimeException;
|
||||
* @property int $actor_id
|
||||
* @property Actor|null $actor
|
||||
* @property string $in_reply_to_id
|
||||
* @property Comment|null $reply_to_comment
|
||||
* @property EpisodeComment|null $reply_to_comment
|
||||
* @property string $message
|
||||
* @property string $message_html
|
||||
* @property int $likes_count
|
||||
* @property int $dislikes_count
|
||||
* @property int $replies_count
|
||||
* @property Time $created_at
|
||||
* @property int $created_by
|
||||
*
|
||||
* @property EpisodeComment[] $replies
|
||||
*/
|
||||
class Comment extends UuidEntity
|
||||
class EpisodeComment extends UuidEntity
|
||||
{
|
||||
protected ?Episode $episode = null;
|
||||
|
||||
protected ?Actor $actor = null;
|
||||
|
||||
protected ?Comment $reply_to_comment = null;
|
||||
protected ?EpisodeComment $reply_to_comment = null;
|
||||
|
||||
/**
|
||||
* @var EpisodeComment[]|null
|
||||
*/
|
||||
protected ?array $replies = null;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
@ -57,7 +64,6 @@ class Comment extends UuidEntity
|
||||
'message' => 'string',
|
||||
'message_html' => 'string',
|
||||
'likes_count' => 'integer',
|
||||
'dislikes_count' => 'integer',
|
||||
'replies_count' => 'integer',
|
||||
'created_by' => 'integer',
|
||||
'is_from_post' => 'boolean',
|
||||
@ -96,6 +102,22 @@ class Comment extends UuidEntity
|
||||
return $this->actor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EpisodeComment[]
|
||||
*/
|
||||
public function getReplies(): array
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Comment must be created before getting replies.');
|
||||
}
|
||||
|
||||
if ($this->replies === null) {
|
||||
$this->replies = (new EpisodeCommentModel())->getCommentReplies($this->id);
|
||||
}
|
||||
|
||||
return $this->replies;
|
||||
}
|
||||
|
||||
public function setMessage(string $message): static
|
||||
{
|
||||
helper('activitypub');
|
33
app/Entities/Like.php
Normal file
33
app/Entities/Like.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2021 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Entities;
|
||||
|
||||
use Michalsn\Uuid\UuidEntity;
|
||||
|
||||
/**
|
||||
* @property int $actor_id
|
||||
* @property string $comment_id
|
||||
*/
|
||||
class Like extends UuidEntity
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $uuids = ['comment_id'];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'actor_id' => 'integer',
|
||||
'comment_id' => 'string',
|
||||
];
|
||||
}
|
@ -16,11 +16,8 @@ return [
|
||||
'submit_reply' => 'Reply',
|
||||
],
|
||||
'like' => 'Like',
|
||||
'dislike' => 'Dislike',
|
||||
'replies' => '{numberOfReplies, plural,
|
||||
one {# reply}
|
||||
other {# replies}
|
||||
}',
|
||||
'reply' => 'Reply',
|
||||
'view_replies' => 'View replies ({numberOfReplies})',
|
||||
'block_actor' => 'Block user @{actorUsername}',
|
||||
'block_domain' => 'Block domain @{actorDomain}',
|
||||
'delete' => 'Delete comment',
|
||||
|
27
app/Language/fr/Comment.php
Normal file
27
app/Language/fr/Comment.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
return [
|
||||
'form' => [
|
||||
'episode_message_placeholder' => 'Saisissez un commentaire...',
|
||||
'reply_to_placeholder' => 'Répondre à @{actorUsername}',
|
||||
'submit' => 'Envoyer !',
|
||||
'submit_reply' => 'Répondre',
|
||||
],
|
||||
'like' => 'J’aime',
|
||||
'reply' => 'Répondre',
|
||||
'replies' => '{numberOfReplies, plural,
|
||||
one {# réponse}
|
||||
other {# réponses}
|
||||
}',
|
||||
'block_actor' => 'Bloquer l’utilisateur @{actorUsername}',
|
||||
'block_domain' => 'Bloquer le domaine @{actorDomain}',
|
||||
'delete' => 'Supprimer le commentaire',
|
||||
];
|
@ -19,7 +19,7 @@ return [
|
||||
'Écrivez votre message pour l’épisode...',
|
||||
'episode_url_placeholder' => 'URL de l’épisode',
|
||||
'reply_to_placeholder' => 'Répondre à @{actorUsername}',
|
||||
'submit' => 'Envoyer!',
|
||||
'submit' => 'Envoyer !',
|
||||
'submit_reply' => 'Répondre',
|
||||
],
|
||||
'favourites' => '{numberOfFavourites, plural,
|
||||
|
@ -100,14 +100,12 @@ class FavouriteModel extends UuidModel
|
||||
->where('id', service('uuid') ->fromString($post->id) ->getBytes())
|
||||
->decrement('favourites_count');
|
||||
|
||||
$this->db
|
||||
->table('activitypub_favourites')
|
||||
->where([
|
||||
'actor_id' => $actor->id,
|
||||
'post_id' => service('uuid')
|
||||
->fromString($post->id)
|
||||
->getBytes(),
|
||||
])
|
||||
$this->where([
|
||||
'actor_id' => $actor->id,
|
||||
'post_id' => service('uuid')
|
||||
->fromString($post->id)
|
||||
->getBytes(),
|
||||
])
|
||||
->delete();
|
||||
|
||||
if ($registerActivity) {
|
||||
@ -161,7 +159,7 @@ class FavouriteModel extends UuidModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or removes favourite from database and increments count
|
||||
* Adds or removes favourite from database
|
||||
*/
|
||||
public function toggleFavourite(Actor $actor, Post $post): void
|
||||
{
|
||||
|
@ -11,7 +11,7 @@ declare(strict_types=1);
|
||||
namespace App\Libraries;
|
||||
|
||||
use ActivityPub\Core\ObjectType;
|
||||
use App\Entities\Comment;
|
||||
use App\Entities\EpisodeComment;
|
||||
|
||||
class CommentObject extends ObjectType
|
||||
{
|
||||
@ -23,7 +23,7 @@ class CommentObject extends ObjectType
|
||||
|
||||
protected string $replies;
|
||||
|
||||
public function __construct(Comment $comment)
|
||||
public function __construct(EpisodeComment $comment)
|
||||
{
|
||||
$this->id = $comment->uri;
|
||||
|
||||
|
@ -11,22 +11,22 @@ declare(strict_types=1);
|
||||
namespace App\Models;
|
||||
|
||||
use ActivityPub\Activities\CreateActivity;
|
||||
use App\Entities\Comment;
|
||||
use App\Entities\EpisodeComment;
|
||||
use App\Libraries\CommentObject;
|
||||
use CodeIgniter\Database\BaseBuilder;
|
||||
use Michalsn\Uuid\UuidModel;
|
||||
|
||||
class CommentModel extends UuidModel
|
||||
class EpisodeCommentModel extends UuidModel
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $returnType = Comment::class;
|
||||
protected $returnType = EpisodeComment::class;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'comments';
|
||||
protected $table = 'episode_comments';
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
@ -45,7 +45,6 @@ class CommentModel extends UuidModel
|
||||
'message',
|
||||
'message_html',
|
||||
'likes_count',
|
||||
'dislikes_count',
|
||||
'replies_count',
|
||||
'created_at',
|
||||
'created_by',
|
||||
@ -56,7 +55,7 @@ class CommentModel extends UuidModel
|
||||
*/
|
||||
protected $beforeInsert = ['setCommentId'];
|
||||
|
||||
public function getCommentById(string $commentId): ?Comment
|
||||
public function getCommentById(string $commentId): ?EpisodeComment
|
||||
{
|
||||
$cacheName = "comment#{$commentId}";
|
||||
if (! ($found = cache($cacheName))) {
|
||||
@ -69,7 +68,7 @@ class CommentModel extends UuidModel
|
||||
return $found;
|
||||
}
|
||||
|
||||
public function addComment(Comment $comment, bool $registerActivity = false): string | false
|
||||
public function addComment(EpisodeComment $comment, bool $registerActivity = false): string | false
|
||||
{
|
||||
$this->db->transStart();
|
||||
// increment Episode's comments_count
|
||||
@ -122,7 +121,9 @@ class CommentModel extends UuidModel
|
||||
/**
|
||||
* Retrieves all published posts for a given episode ordered by publication date
|
||||
*
|
||||
* @return Comment[]
|
||||
* @return EpisodeComment[]
|
||||
*
|
||||
* @noRector ReturnTypeDeclarationRector
|
||||
*/
|
||||
public function getEpisodeComments(int $episodeId): array
|
||||
{
|
||||
@ -133,7 +134,7 @@ class CommentModel extends UuidModel
|
||||
|
||||
$episodePostsReplies = $this->db->table('activitypub_posts')
|
||||
->select(
|
||||
'id, uri, episode_id, actor_id, in_reply_to_id, message, message_html, favourites_count as likes_count, 0 as dislikes_count, replies_count, published_at as created_at, created_by, 1 as is_from_post'
|
||||
'id, uri, episode_id, actor_id, in_reply_to_id, message, message_html, favourites_count as likes_count, replies_count, published_at as created_at, created_by, 1 as is_from_post'
|
||||
)
|
||||
->whereIn('in_reply_to_id', function (BaseBuilder $builder) use (&$episodeId): BaseBuilder {
|
||||
return $builder->select('id')
|
||||
@ -147,18 +148,25 @@ class CommentModel extends UuidModel
|
||||
$episodeComments . ' UNION ' . $episodePostsReplies . ' ORDER BY created_at ASC'
|
||||
);
|
||||
|
||||
return $allEpisodeComments->getCustomResultObject($this->returnType);
|
||||
// FIXME:?
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->convertUuidFieldsToStrings(
|
||||
$allEpisodeComments->getCustomResultObject($this->tempReturnType),
|
||||
$this->tempReturnType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all replies for a given comment
|
||||
*
|
||||
* @return Comment[]
|
||||
* @return EpisodeComment[]
|
||||
*/
|
||||
public function getCommentReplies(int $episodeId, string $commentId): array
|
||||
public function getCommentReplies(string $commentId): array
|
||||
{
|
||||
// TODO: get all replies for a given comment
|
||||
return $this->findAll();
|
||||
return $this->where('in_reply_to_id', $this->uuid->fromString($commentId)->getBytes())
|
||||
->orderBy('created_at', 'ASC')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
/**
|
165
app/Models/LikeModel.php
Normal file
165
app/Models/LikeModel.php
Normal file
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2021 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use ActivityPub\Activities\LikeActivity;
|
||||
use ActivityPub\Activities\UndoActivity;
|
||||
use ActivityPub\Entities\Actor;
|
||||
use App\Entities\EpisodeComment;
|
||||
use App\Entities\Like;
|
||||
use Michalsn\Uuid\UuidModel;
|
||||
|
||||
class LikeModel extends UuidModel
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'likes';
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $uuidFields = ['comment_id'];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $allowedFields = ['actor_id', 'comment_id'];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $returnType = Like::class;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $useTimestamps = true;
|
||||
|
||||
protected $updatedField;
|
||||
|
||||
public function addLike(Actor $actor, EpisodeComment $comment, bool $registerActivity = true): void
|
||||
{
|
||||
$this->db->transStart();
|
||||
|
||||
$this->insert([
|
||||
'actor_id' => $actor->id,
|
||||
'comment_id' => $comment->id,
|
||||
]);
|
||||
|
||||
(new EpisodeCommentModel())
|
||||
->where('id', service('uuid')->fromString($comment->id)->getBytes())
|
||||
->increment('likes_count');
|
||||
|
||||
if ($registerActivity) {
|
||||
$likeActivity = new LikeActivity();
|
||||
$likeActivity->set('actor', $actor->uri)
|
||||
->set('object', $comment->uri);
|
||||
|
||||
$activityId = model('ActivityModel')
|
||||
->newActivity(
|
||||
'Like',
|
||||
$actor->id,
|
||||
null,
|
||||
null,
|
||||
$likeActivity->toJSON(),
|
||||
$comment->created_at,
|
||||
'queued',
|
||||
);
|
||||
|
||||
$likeActivity->set('id', url_to('activity', $actor->username, $activityId));
|
||||
|
||||
model('ActivityModel')
|
||||
->update($activityId, [
|
||||
'payload' => $likeActivity->toJSON(),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->db->transComplete();
|
||||
}
|
||||
|
||||
public function removeLike(Actor $actor, EpisodeComment $comment, bool $registerActivity = true): void
|
||||
{
|
||||
$this->db->transStart();
|
||||
|
||||
(new EpisodeCommentModel())
|
||||
->where('id', service('uuid') ->fromString($comment->id) ->getBytes())
|
||||
->decrement('likes_count');
|
||||
|
||||
$this->where([
|
||||
'actor_id' => $actor->id,
|
||||
'comment_id' => service('uuid')
|
||||
->fromString($comment->id)
|
||||
->getBytes(),
|
||||
])
|
||||
->delete();
|
||||
|
||||
if ($registerActivity) {
|
||||
$undoActivity = new UndoActivity();
|
||||
// FIXME: get like activity associated with the deleted like
|
||||
$activity = model('ActivityModel')
|
||||
->where([
|
||||
'type' => 'Like',
|
||||
'actor_id' => $actor->id,
|
||||
])
|
||||
->first();
|
||||
|
||||
$likeActivity = new LikeActivity();
|
||||
$likeActivity
|
||||
->set('id', url_to('activity', $actor->username, $activity->id))
|
||||
->set('actor', $actor->uri)
|
||||
->set('object', $comment->uri);
|
||||
|
||||
$undoActivity
|
||||
->set('actor', $actor->uri)
|
||||
->set('object', $likeActivity);
|
||||
|
||||
$activityId = model('ActivityModel')
|
||||
->newActivity(
|
||||
'Undo',
|
||||
$actor->id,
|
||||
null,
|
||||
null,
|
||||
$undoActivity->toJSON(),
|
||||
$comment->created_at,
|
||||
'queued',
|
||||
);
|
||||
|
||||
$undoActivity->set('id', url_to('activity', $actor->username, $activityId));
|
||||
|
||||
model('ActivityModel')
|
||||
->update($activityId, [
|
||||
'payload' => $undoActivity->toJSON(),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->db->transComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or removes likes from database
|
||||
*/
|
||||
public function toggleLike(Actor $actor, EpisodeComment $comment): void
|
||||
{
|
||||
if (
|
||||
$this->where([
|
||||
'actor_id' => $actor->id,
|
||||
'comment_id' => service('uuid')
|
||||
->fromString($comment->id)
|
||||
->getBytes(),
|
||||
])->first()
|
||||
) {
|
||||
$this->removeLike($actor, $comment);
|
||||
} else {
|
||||
$this->addLike($actor, $comment);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<g>
|
||||
<path fill="none" d="M0 0h24v24H0z"/>
|
||||
<path d="M22 15h-3V3h3a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zm-5.293 1.293l-6.4 6.4a.5.5 0 0 1-.654.047L8.8 22.1a1.5 1.5 0 0 1-.553-1.57L9.4 16H3a2 2 0 0 1-2-2v-2.104a2 2 0 0 1 .15-.762L4.246 3.62A1 1 0 0 1 5.17 3H16a1 1 0 0 1 1 1v11.586a1 1 0 0 1-.293.707z"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 393 B |
@ -1,6 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<g>
|
||||
<path fill="none" d="M0 0h24v24H0z"/>
|
||||
<path d="M2 9h3v12H2a1 1 0 0 1-1-1V10a1 1 0 0 1 1-1zm5.293-1.293l6.4-6.4a.5.5 0 0 1 .654-.047l.853.64a1.5 1.5 0 0 1 .553 1.57L14.6 8H21a2 2 0 0 1 2 2v2.104a2 2 0 0 1-.15.762l-3.095 7.515a1 1 0 0 1-.925.619H8a1 1 0 0 1-1-1V8.414a1 1 0 0 1 .293-.707z"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 391 B |
@ -1,7 +1,7 @@
|
||||
<article class="relative z-10 flex w-full px-4 py-2 rounded-2xl">
|
||||
<img src="<?= $comment->actor->avatar_image_url ?>" alt="<?= $comment->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
|
||||
<div class="flex-1">
|
||||
<header class="w-full mb-2">
|
||||
<header class="w-full mb-2 text-sm">
|
||||
<a href="<?= $comment->actor
|
||||
->uri ?>" class="flex items-baseline hover:underline" <?= $comment->actor->is_local
|
||||
? ''
|
||||
@ -17,27 +17,10 @@
|
||||
</a>
|
||||
</header>
|
||||
<div class="mb-2 post-content"><?= $comment->message_html ?></div>
|
||||
<div class="inline-flex gap-x-4">
|
||||
<?= anchor_popup(
|
||||
route_to('comment-remote-action', $podcast->handle, $episode->slug, $comment->id, 'like'),
|
||||
icon('thumb-up', 'text-lg mr-1 text-gray-400 group-hover:text-gray-600') . 0,
|
||||
[
|
||||
'class' => 'inline-flex items-center hover:underline group',
|
||||
'width' => 420,
|
||||
'height' => 620,
|
||||
'title' => lang('Comment.like'),
|
||||
],
|
||||
) ?>
|
||||
<?= anchor_popup(
|
||||
route_to('comment-remote-action', $podcast->handle, $episode->slug, $comment->id, 'dislike'),
|
||||
icon('thumb-down', 'text-lg text-gray-400 group-hover:text-gray-600'),
|
||||
[
|
||||
'class' => 'inline-flex items-center hover:underline group',
|
||||
'width' => 420,
|
||||
'height' => 620,
|
||||
'title' => lang('Comment.dislike'),
|
||||
],
|
||||
) ?>
|
||||
</div>
|
||||
<?php if ($comment->is_from_post): ?>
|
||||
<?= $this->include('podcast/_partials/comment_actions_from_post') ?>
|
||||
<?php else: ?>
|
||||
<?= $this->include('podcast/_partials/comment_actions') ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</article>
|
||||
|
24
app/Views/podcast/_partials/comment_actions.php
Normal file
24
app/Views/podcast/_partials/comment_actions.php
Normal file
@ -0,0 +1,24 @@
|
||||
<footer>
|
||||
<form action="<?= route_to('comment-attempt-like', interact_as_actor()->username, $episode->slug, $comment->id) ?>" method="POST" class="flex items-center gap-x-4">
|
||||
<button type="submit" name="action" value="favourite" class="inline-flex items-center hover:underline group" title="<?= lang(
|
||||
'Comment.like',
|
||||
[
|
||||
'numberOfLikes' => $comment->likes_count,
|
||||
],
|
||||
) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . $comment->likes_count ?></button>
|
||||
<?= button(
|
||||
lang('Comment.reply'),
|
||||
route_to('comment', $podcast->handle, $episode->slug, $comment->id),
|
||||
[
|
||||
'size' => 'small',
|
||||
],
|
||||
) ?>
|
||||
</form>
|
||||
<?php if($comment->replies_count): ?>
|
||||
<?= anchor(
|
||||
route_to('comment', $podcast->handle, $episode->slug, $comment->id),
|
||||
icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', ['numberOfReplies' => $comment->replies_count]),
|
||||
['class' => 'inline-flex items-center text-xs hover:underline']
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
</footer>
|
24
app/Views/podcast/_partials/comment_actions_from_post.php
Normal file
24
app/Views/podcast/_partials/comment_actions_from_post.php
Normal file
@ -0,0 +1,24 @@
|
||||
<footer>
|
||||
<form action="<?= route_to('post-attempt-action', interact_as_actor()->username, $comment->id) ?>" method="POST" class="flex items-center gap-x-4">
|
||||
<button type="submit" name="action" value="favourite" class="inline-flex items-center hover:underline group" title="<?= lang(
|
||||
'Comment.like',
|
||||
[
|
||||
'numberOfLikes' => $comment->likes_count,
|
||||
],
|
||||
) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . $comment->likes_count ?></button>
|
||||
<?= button(
|
||||
lang('Comment.reply'),
|
||||
route_to('post', $podcast->handle, $comment->id),
|
||||
[
|
||||
'size' => 'small',
|
||||
],
|
||||
) ?>
|
||||
</form>
|
||||
<?php if($comment->replies_count): ?>
|
||||
<?= anchor(
|
||||
route_to('post', $podcast->handle, $comment->id),
|
||||
icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', ['numberOfReplies' => $comment->replies_count]),
|
||||
['class' => 'inline-flex items-center text-xs hover:underline']
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
</footer>
|
27
app/Views/podcast/_partials/comment_authenticated.php
Normal file
27
app/Views/podcast/_partials/comment_authenticated.php
Normal file
@ -0,0 +1,27 @@
|
||||
<article class="relative z-10 w-full bg-white shadow-md rounded-2xl">
|
||||
<header class="flex px-6 py-4">
|
||||
<img src="<?= $comment->actor
|
||||
->avatar_image_url ?>" alt="<?= $comment->actor->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
|
||||
<div class="flex flex-col min-w-0">
|
||||
<a href="<?= $comment->actor
|
||||
->uri ?>" class="flex items-baseline hover:underline" <?= $comment
|
||||
->actor->is_local
|
||||
? ''
|
||||
: 'target="_blank" rel="noopener noreferrer"' ?>>
|
||||
<span class="mr-2 font-semibold truncate"><?= $comment->actor
|
||||
->display_name ?></span>
|
||||
<span class="text-sm text-gray-500 truncate">@<?= $comment->actor
|
||||
->username .
|
||||
($comment->actor->is_local
|
||||
? ''
|
||||
: '@' . $comment->actor->domain) ?></span>
|
||||
</a>
|
||||
<a href="<?= route_to('comment', $podcast->handle, $episode->slug, $comment->id) ?>"
|
||||
class="text-xs text-gray-500">
|
||||
<?= relative_time($comment->created_at) ?>
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
<div class="px-6 mb-4 post-content"><?= $comment->message_html ?></div>
|
||||
<?= $this->include('podcast/_partials/comment_actions') ?>
|
||||
</article>
|
22
app/Views/podcast/_partials/comment_with_replies.php
Normal file
22
app/Views/podcast/_partials/comment_with_replies.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?= $this->include('podcast/_partials/comment') ?>
|
||||
<div class="-mt-2 overflow-hidden border-b border-l border-r comment-replies rounded-b-xl">
|
||||
|
||||
<div class="px-6 pt-8 pb-4 bg-gray-50">
|
||||
<?= anchor_popup(
|
||||
route_to('comment-remote-action', $podcast->handle, $comment->id, 'reply'),
|
||||
lang('comment.reply_to', ['actorUsername' => $comment->actor->username]),
|
||||
[
|
||||
'class' =>
|
||||
'text-center justify-center font-semibold rounded-full shadow relative z-10 px-4 py-2 w-full bg-rose-600 text-white inline-flex items-center hover:bg-rose-700',
|
||||
'width' => 420,
|
||||
'height' => 620,
|
||||
],
|
||||
) ?>
|
||||
</div>
|
||||
|
||||
|
||||
<?php foreach ($comment->replies as $reply): ?>
|
||||
<?= view('podcast/_partials/comment', ['comment' => $reply]) ?>
|
||||
<?php endforeach; ?>
|
||||
|
||||
</div>
|
@ -0,0 +1,47 @@
|
||||
<?= $this->include('podcast/_partials/comment_authenticated') ?>
|
||||
<div class="-mt-2 overflow-hidden border-b border-l border-r post-replies rounded-b-xl">
|
||||
<?= form_open(
|
||||
route_to('comment-attempt-action', interact_as_actor()->username, $episode->slug, $comment->id),
|
||||
[
|
||||
'class' => 'bg-gray-50 flex px-6 pt-8 pb-4',
|
||||
],
|
||||
) ?>
|
||||
<img src="<?= interact_as_actor()
|
||||
->avatar_image_url ?>" alt="<?= interact_as_actor()
|
||||
->display_name ?>" class="w-12 h-12 mr-4 rounded-full ring-gray-50 ring-2" />
|
||||
<div class="flex flex-col flex-1">
|
||||
<?= form_textarea(
|
||||
[
|
||||
'id' => 'message',
|
||||
'name' => 'message',
|
||||
'class' => 'form-textarea mb-4 w-full',
|
||||
'required' => 'required',
|
||||
'placeholder' => lang('Comment.form.reply_to_placeholder', [
|
||||
'actorUsername' => $comment->actor->username,
|
||||
]),
|
||||
],
|
||||
old('message', '', false),
|
||||
[
|
||||
'rows' => 1,
|
||||
],
|
||||
) ?>
|
||||
<?= button(
|
||||
lang('Comment.form.submit_reply'),
|
||||
'',
|
||||
['variant' => 'primary', 'size' => 'small'],
|
||||
[
|
||||
'type' => 'submit',
|
||||
'class' => 'self-end',
|
||||
'name' => 'action',
|
||||
'value' => 'reply',
|
||||
],
|
||||
) ?>
|
||||
</div>
|
||||
<?= form_close() ?>
|
||||
|
||||
<?php foreach ($comment->replies as $reply): ?>
|
||||
<?= view('podcast/_partials/comment_authenticated', [
|
||||
'comment' => $reply,
|
||||
]) ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
@ -1,7 +1,7 @@
|
||||
<article class="relative z-10 w-full bg-white shadow rounded-2xl">
|
||||
<header class="flex px-6 py-4">
|
||||
<img src="<?= $post->actor
|
||||
->avatar_image_url ?>" alt="<?= $post->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
|
||||
->avatar_image_url ?>" alt="<?= $post->actor->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
|
||||
<div class="flex flex-col min-w-0">
|
||||
<a href="<?= $post->actor
|
||||
->uri ?>" class="flex items-baseline hover:underline" <?= $post
|
||||
|
@ -1,7 +1,7 @@
|
||||
<article class="relative z-10 w-full bg-white shadow-md rounded-2xl">
|
||||
<header class="flex px-6 py-4">
|
||||
<img src="<?= $post->actor
|
||||
->avatar_image_url ?>" alt="<?= $post->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
|
||||
->avatar_image_url ?>" alt="<?= $post->actor->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
|
||||
<div class="flex flex-col min-w-0">
|
||||
<a href="<?= $post->actor
|
||||
->uri ?>" class="flex items-baseline hover:underline" <?= $post
|
||||
|
38
app/Views/podcast/comment.php
Normal file
38
app/Views/podcast/comment.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?= $this->extend('podcast/_layout') ?>
|
||||
|
||||
<?= $this->section('meta-tags') ?>
|
||||
<title><?= lang('Comment.title', [
|
||||
'actorDisplayName' => $comment->actor->display_name,
|
||||
]) ?></title>
|
||||
<meta name="description" content="<?= $comment->message ?>"/>
|
||||
<meta property="og:title" content="<?= lang('Comment.title', [
|
||||
'actorDisplayName' => $comment->actor->display_name,
|
||||
]) ?>"/>
|
||||
<meta property="og:locale" content="<?= service(
|
||||
'request',
|
||||
)->getLocale() ?>" />
|
||||
<meta property="og:site_name" content="<?= $comment->actor->display_name ?>" />
|
||||
<meta property="og:url" content="<?= current_url() ?>" />
|
||||
<meta property="og:image" content="<?= $comment->actor->avatar_image_url ?>" />
|
||||
<meta property="og:description" content="<?= $comment->message ?>" />
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="max-w-2xl px-6 mx-auto">
|
||||
<nav class="py-3">
|
||||
<a href="<?= route_to('episode', $podcast->handle, $episode->slug) ?>"
|
||||
class="inline-flex items-center px-4 py-2 text-sm"><?= icon(
|
||||
'arrow-left',
|
||||
'mr-2 text-lg',
|
||||
) .
|
||||
lang('Comment.back_to_episode', [
|
||||
'actor' => $comment->actor->display_name,
|
||||
]) ?></a>
|
||||
</nav>
|
||||
<div class="pb-12">
|
||||
<?= $this->include('podcast/_partials/comment_with_replies') ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= $this->endSection()
|
||||
?>
|
40
app/Views/podcast/comment_authenticated.php
Normal file
40
app/Views/podcast/comment_authenticated.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?= $this->extend('podcast/_layout_authenticated') ?>
|
||||
|
||||
<?= $this->section('meta-tags') ?>
|
||||
<title><?= lang('Comment.title', [
|
||||
'actorDisplayName' => $comment->actor->display_name,
|
||||
]) ?></title>
|
||||
<meta name="description" content="<?= $comment->message ?>"/>
|
||||
<meta property="og:title" content="<?= lang('Comment.title', [
|
||||
'actorDisplayName' => $comment->actor->display_name,
|
||||
]) ?>"/>
|
||||
<meta property="og:locale" content="<?= service(
|
||||
'request',
|
||||
)->getLocale() ?>" />
|
||||
<meta property="og:site_name" content="<?= $comment->actor->display_name ?>" />
|
||||
<meta property="og:url" content="<?= current_url() ?>" />
|
||||
<meta property="og:image" content="<?= $comment->actor->avatar_image_url ?>" />
|
||||
<meta property="og:description" content="<?= $comment->message ?>" />
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="max-w-2xl px-6 mx-auto">
|
||||
<nav class="py-3">
|
||||
<a href="<?= route_to('episode', $podcast->handle, $episode->slug) ?>"
|
||||
class="inline-flex items-center px-4 py-2 text-sm"><?= icon(
|
||||
'arrow-left',
|
||||
'mr-2 text-lg',
|
||||
) .
|
||||
lang('Comment.back_to_episode', [
|
||||
'actor' => $comment->actor->display_name,
|
||||
]) ?></a>
|
||||
</nav>
|
||||
<div class="pb-12">
|
||||
<?= $this->include(
|
||||
'podcast/_partials/comment_with_replies_authenticated',
|
||||
) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= $this->endSection()
|
||||
?>
|
@ -85,7 +85,7 @@
|
||||
<div class="tab-panels">
|
||||
<section id="comments" class="space-y-6 tab-panel">
|
||||
<?= form_open(route_to('comment-attempt-create', $podcast->id, $episode->id), [
|
||||
'class' => 'flex p-4 bg-white shadow rounded-xl',
|
||||
'class' => 'flex p-4',
|
||||
]) ?>
|
||||
<?= csrf_field() ?>
|
||||
|
||||
@ -118,7 +118,6 @@
|
||||
) ?>
|
||||
</div>
|
||||
<?= form_close() ?>
|
||||
<hr class="my-4 border border-pine-100">
|
||||
<?php foreach ($episode->comments as $comment): ?>
|
||||
<?= view('podcast/_partials/comment', ['comment' => $comment]) ?>
|
||||
<?php endforeach; ?>
|
||||
|
Loading…
x
Reference in New Issue
Block a user