feat(comments): add comments to episodes + update naming of status to post
- remove confusing counts for episode (total favourites, total reblogs) - add comments section to episode page to display episode comments + post replies linked to the episode
This commit is contained in:
parent
3ff1364906
commit
bb4752c35e
|
@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||
namespace Config;
|
||||
|
||||
use App\Entities\Actor;
|
||||
use App\Entities\Status;
|
||||
use App\Entities\Post;
|
||||
use App\Entities\User;
|
||||
use CodeIgniter\Events\Events;
|
||||
use CodeIgniter\Exceptions\FrameworkException;
|
||||
|
@ -120,82 +120,72 @@ Events::on('on_undo_follow', function ($actor, $targetActor): void {
|
|||
});
|
||||
|
||||
/**
|
||||
* @param Status $status
|
||||
* @param Post $post
|
||||
*/
|
||||
Events::on('on_status_add', function ($status): void {
|
||||
if ($status->in_reply_to_id !== null) {
|
||||
$status = $status->reply_to_status;
|
||||
Events::on('on_post_add', function ($post): void {
|
||||
$isReply = $post->in_reply_to_id !== null;
|
||||
|
||||
if ($isReply) {
|
||||
$post = $post->reply_to_post;
|
||||
}
|
||||
|
||||
if ($status->episode_id) {
|
||||
model('EpisodeModel')
|
||||
->where('id', $status->episode_id)
|
||||
->increment('statuses_total');
|
||||
if ($post->episode_id !== null) {
|
||||
if ($isReply) {
|
||||
model('EpisodeModel', false)
|
||||
->where('id', $post->episode_id)
|
||||
->increment('comments_count');
|
||||
} else {
|
||||
model('EpisodeModel', false)
|
||||
->where('id', $post->episode_id)
|
||||
->increment('posts_count');
|
||||
}
|
||||
}
|
||||
|
||||
if ($status->actor->is_podcast) {
|
||||
if ($post->actor->is_podcast) {
|
||||
// Removing all of the podcast pages is a bit overkill, but works to avoid caching bugs
|
||||
// same for other events below
|
||||
cache()
|
||||
->deleteMatching("podcast#{$status->actor->podcast->id}*");
|
||||
->deleteMatching("podcast#{$post->actor->podcast->id}*");
|
||||
cache()
|
||||
->deleteMatching("page_podcast#{$status->actor->podcast->id}*");
|
||||
->deleteMatching("page_podcast#{$post->actor->podcast->id}*");
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @param Status $status
|
||||
* @param Post $post
|
||||
*/
|
||||
Events::on('on_status_remove', function ($status): void {
|
||||
if ($status->in_reply_to_id !== null) {
|
||||
Events::trigger('on_status_remove', $status->reply_to_status);
|
||||
Events::on('on_post_remove', function ($post): void {
|
||||
if ($post->in_reply_to_id !== null) {
|
||||
Events::trigger('on_post_remove', $post->reply_to_post);
|
||||
}
|
||||
|
||||
if ($episodeId = $status->episode_id) {
|
||||
model('EpisodeModel')
|
||||
if ($episodeId = $post->episode_id) {
|
||||
model('EpisodeModel', false)
|
||||
->where('id', $episodeId)
|
||||
->decrement('statuses_total', 1 + $status->reblogs_count);
|
||||
|
||||
model('EpisodeModel')
|
||||
->where('id', $episodeId)
|
||||
->decrement('reblogs_total', $status->reblogs_count);
|
||||
|
||||
model('EpisodeModel')
|
||||
->where('id', $episodeId)
|
||||
->decrement('favourites_total', $status->favourites_count);
|
||||
->decrement('posts_count');
|
||||
}
|
||||
|
||||
if ($status->actor->is_podcast) {
|
||||
if ($post->actor->is_podcast) {
|
||||
cache()
|
||||
->deleteMatching("podcast#{$status->actor->podcast->id}*");
|
||||
->deleteMatching("podcast#{$post->actor->podcast->id}*");
|
||||
cache()
|
||||
->deleteMatching("page_podcast#{$status->actor->podcast->id}*");
|
||||
->deleteMatching("page_podcast#{$post->actor->podcast->id}*");
|
||||
}
|
||||
|
||||
cache()
|
||||
->deleteMatching("page_status#{$status->id}*");
|
||||
->deleteMatching("page_post#{$post->id}*");
|
||||
});
|
||||
|
||||
/**
|
||||
* @param Actor $actor
|
||||
* @param Status $status
|
||||
* @param Post $post
|
||||
*/
|
||||
Events::on('on_status_reblog', function ($actor, $status): void {
|
||||
if ($episodeId = $status->episode_id) {
|
||||
model('EpisodeModel')
|
||||
->where('id', $episodeId)
|
||||
->increment('reblogs_total');
|
||||
|
||||
model('EpisodeModel')
|
||||
->where('id', $episodeId)
|
||||
->increment('statuses_total');
|
||||
}
|
||||
|
||||
if ($status->actor->is_podcast) {
|
||||
Events::on('on_post_reblog', function ($actor, $post): void {
|
||||
if ($post->actor->is_podcast) {
|
||||
cache()
|
||||
->deleteMatching("podcast#{$status->actor->podcast->id}*");
|
||||
->deleteMatching("podcast#{$post->actor->podcast->id}*");
|
||||
cache()
|
||||
->deleteMatching("page_podcast#{$status->actor->podcast->id}*");
|
||||
->deleteMatching("page_podcast#{$post->actor->podcast->id}*");
|
||||
}
|
||||
|
||||
if ($actor->is_podcast) {
|
||||
|
@ -205,111 +195,96 @@ Events::on('on_status_reblog', function ($actor, $status): void {
|
|||
}
|
||||
|
||||
cache()
|
||||
->deleteMatching("page_status#{$status->id}*");
|
||||
->deleteMatching("page_post#{$post->id}*");
|
||||
|
||||
if ($status->in_reply_to_id !== null) {
|
||||
cache()->deleteMatching("page_status#{$status->in_reply_to_id}");
|
||||
if ($post->in_reply_to_id !== null) {
|
||||
cache()->deleteMatching("page_post#{$post->in_reply_to_id}");
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @param Status $reblogStatus
|
||||
* @param Post $reblogPost
|
||||
*/
|
||||
Events::on('on_status_undo_reblog', function ($reblogStatus): void {
|
||||
$status = $reblogStatus->reblog_of_status;
|
||||
if ($episodeId = $status->episode_id) {
|
||||
model('EpisodeModel')
|
||||
->where('id', $episodeId)
|
||||
->decrement('reblogs_total');
|
||||
Events::on('on_post_undo_reblog', function ($reblogPost): void {
|
||||
$post = $reblogPost->reblog_of_post;
|
||||
|
||||
model('EpisodeModel')
|
||||
->where('id', $episodeId)
|
||||
->decrement('statuses_total');
|
||||
}
|
||||
|
||||
if ($status->actor->is_podcast) {
|
||||
if ($post->actor->is_podcast) {
|
||||
cache()
|
||||
->deleteMatching("podcast#{$status->actor->podcast->id}*");
|
||||
->deleteMatching("podcast#{$post->actor->podcast->id}*");
|
||||
cache()
|
||||
->deleteMatching("page_podcast#{$status->actor->podcast->id}*");
|
||||
->deleteMatching("page_podcast#{$post->actor->podcast->id}*");
|
||||
}
|
||||
|
||||
cache()
|
||||
->deleteMatching("page_status#{$status->id}*");
|
||||
->deleteMatching("page_post#{$post->id}*");
|
||||
cache()
|
||||
->deleteMatching("page_status#{$reblogStatus->id}*");
|
||||
->deleteMatching("page_post#{$reblogPost->id}*");
|
||||
|
||||
if ($status->in_reply_to_id !== null) {
|
||||
cache()->deleteMatching("page_status#{$status->in_reply_to_id}");
|
||||
if ($post->in_reply_to_id !== null) {
|
||||
cache()->deleteMatching("page_post#{$post->in_reply_to_id}");
|
||||
}
|
||||
|
||||
if ($reblogStatus->actor->is_podcast) {
|
||||
if ($reblogPost->actor->is_podcast) {
|
||||
cache()
|
||||
->deleteMatching("podcast#{$reblogStatus->actor->podcast->id}*");
|
||||
->deleteMatching("podcast#{$reblogPost->actor->podcast->id}*");
|
||||
cache()
|
||||
->deleteMatching("page_podcast#{$reblogStatus->actor->podcast->id}*");
|
||||
->deleteMatching("page_podcast#{$reblogPost->actor->podcast->id}*");
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @param Status $reply
|
||||
* @param Post $reply
|
||||
*/
|
||||
Events::on('on_status_reply', function ($reply): void {
|
||||
$status = $reply->reply_to_status;
|
||||
Events::on('on_post_reply', function ($reply): void {
|
||||
$post = $reply->reply_to_post;
|
||||
|
||||
if ($status->actor->is_podcast) {
|
||||
if ($post->actor->is_podcast) {
|
||||
cache()
|
||||
->deleteMatching("podcast#{$status->actor->podcast->id}*");
|
||||
->deleteMatching("podcast#{$post->actor->podcast->id}*");
|
||||
cache()
|
||||
->deleteMatching("page_podcast#{$status->actor->podcast->id}*");
|
||||
->deleteMatching("page_podcast#{$post->actor->podcast->id}*");
|
||||
}
|
||||
|
||||
cache()
|
||||
->deleteMatching("page_status#{$status->id}*");
|
||||
->deleteMatching("page_post#{$post->id}*");
|
||||
});
|
||||
|
||||
/**
|
||||
* @param Status $reply
|
||||
* @param Post $reply
|
||||
*/
|
||||
Events::on('on_reply_remove', function ($reply): void {
|
||||
$status = $reply->reply_to_status;
|
||||
$post = $reply->reply_to_post;
|
||||
|
||||
if ($status->actor->is_podcast) {
|
||||
if ($post->actor->is_podcast) {
|
||||
cache()
|
||||
->deleteMatching("page_podcast#{$status->actor->podcast->id}*");
|
||||
->deleteMatching("page_podcast#{$post->actor->podcast->id}*");
|
||||
cache()
|
||||
->deleteMatching("podcast#{$status->actor->podcast->id}*");
|
||||
->deleteMatching("podcast#{$post->actor->podcast->id}*");
|
||||
}
|
||||
|
||||
cache()
|
||||
->deleteMatching("page_status#{$status->id}*");
|
||||
->deleteMatching("page_post#{$post->id}*");
|
||||
cache()
|
||||
->deleteMatching("page_status#{$reply->id}*");
|
||||
->deleteMatching("page_post#{$reply->id}*");
|
||||
});
|
||||
|
||||
/**
|
||||
* @param Actor $actor
|
||||
* @param Status $status
|
||||
* @param Post $post
|
||||
*/
|
||||
Events::on('on_status_favourite', function ($actor, $status): void {
|
||||
if ($status->episode_id) {
|
||||
model('EpisodeModel')
|
||||
->where('id', $status->episode_id)
|
||||
->increment('favourites_total');
|
||||
}
|
||||
|
||||
if ($status->actor->is_podcast) {
|
||||
Events::on('on_post_favourite', function ($actor, $post): void {
|
||||
if ($post->actor->is_podcast) {
|
||||
cache()
|
||||
->deleteMatching("podcast#{$status->actor->podcast->id}*");
|
||||
->deleteMatching("podcast#{$post->actor->podcast->id}*");
|
||||
cache()
|
||||
->deleteMatching("page_podcast#{$status->actor->podcast->id}*");
|
||||
->deleteMatching("page_podcast#{$post->actor->podcast->id}*");
|
||||
}
|
||||
|
||||
cache()
|
||||
->deleteMatching("page_status#{$status->id}*");
|
||||
->deleteMatching("page_post#{$post->id}*");
|
||||
|
||||
if ($status->in_reply_to_id !== null) {
|
||||
cache()->deleteMatching("page_status#{$status->in_reply_to_id}*");
|
||||
if ($post->in_reply_to_id !== null) {
|
||||
cache()->deleteMatching("page_post#{$post->in_reply_to_id}*");
|
||||
}
|
||||
|
||||
if ($actor->is_podcast) {
|
||||
|
@ -321,27 +296,21 @@ Events::on('on_status_favourite', function ($actor, $status): void {
|
|||
|
||||
/**
|
||||
* @param Actor $actor
|
||||
* @param Status $status
|
||||
* @param Post $post
|
||||
*/
|
||||
Events::on('on_status_undo_favourite', function ($actor, $status): void {
|
||||
if ($status->episode_id) {
|
||||
model('EpisodeModel')
|
||||
->where('id', $status->episode_id)
|
||||
->decrement('favourites_total');
|
||||
}
|
||||
|
||||
if ($status->actor->is_podcast) {
|
||||
Events::on('on_post_undo_favourite', function ($actor, $post): void {
|
||||
if ($post->actor->is_podcast) {
|
||||
cache()
|
||||
->deleteMatching("podcast#{$status->actor->podcast->id}*");
|
||||
->deleteMatching("podcast#{$post->actor->podcast->id}*");
|
||||
cache()
|
||||
->deleteMatching("page_podcast#{$status->actor->podcast->id}*");
|
||||
->deleteMatching("page_podcast#{$post->actor->podcast->id}*");
|
||||
}
|
||||
|
||||
cache()
|
||||
->deleteMatching("page_status#{$status->id}*");
|
||||
->deleteMatching("page_post#{$post->id}*");
|
||||
|
||||
if ($status->in_reply_to_id !== null) {
|
||||
cache()->deleteMatching("page_status#{$status->in_reply_to_id}*");
|
||||
if ($post->in_reply_to_id !== null) {
|
||||
cache()->deleteMatching("page_post#{$post->in_reply_to_id}*");
|
||||
}
|
||||
|
||||
if ($actor->is_podcast) {
|
||||
|
@ -356,7 +325,7 @@ Events::on('on_block_actor', function (int $actorId): void {
|
|||
cache()
|
||||
->deleteMatching('podcast*');
|
||||
cache()
|
||||
->deleteMatching('page_status*');
|
||||
->deleteMatching('page_post*');
|
||||
});
|
||||
|
||||
Events::on('on_unblock_actor', function (int $actorId): void {
|
||||
|
@ -364,7 +333,7 @@ Events::on('on_unblock_actor', function (int $actorId): void {
|
|||
cache()
|
||||
->deleteMatching('podcast*');
|
||||
cache()
|
||||
->deleteMatching('page_status*');
|
||||
->deleteMatching('page_post*');
|
||||
});
|
||||
|
||||
Events::on('on_block_domain', function (string $domainName): void {
|
||||
|
@ -372,7 +341,7 @@ Events::on('on_block_domain', function (string $domainName): void {
|
|||
cache()
|
||||
->deleteMatching('podcast*');
|
||||
cache()
|
||||
->deleteMatching('page_status*');
|
||||
->deleteMatching('page_post*');
|
||||
});
|
||||
|
||||
Events::on('on_unblock_domain', function (string $domainName): void {
|
||||
|
@ -380,5 +349,5 @@ Events::on('on_unblock_domain', function (string $domainName): void {
|
|||
cache()
|
||||
->deleteMatching('podcast*');
|
||||
cache()
|
||||
->deleteMatching('page_status*');
|
||||
->deleteMatching('page_post*');
|
||||
});
|
||||
|
|
|
@ -32,10 +32,10 @@ $routes->setAutoRoute(false);
|
|||
*/
|
||||
|
||||
$routes->addPlaceholder('podcastHandle', '[a-zA-Z0-9\_]{1,32}');
|
||||
$routes->addPlaceholder('slug', '[a-zA-Z0-9\-]{1,191}');
|
||||
$routes->addPlaceholder('slug', '[a-zA-Z0-9\-]{1,128}');
|
||||
$routes->addPlaceholder('base64', '[A-Za-z0-9\.\_]+\-{0,2}');
|
||||
$routes->addPlaceholder('platformType', '\bpodcasting|\bsocial|\bfunding');
|
||||
$routes->addPlaceholder('statusAction', '\bfavourite|\breblog|\breply');
|
||||
$routes->addPlaceholder('postAction', '\bfavourite|\breblog|\breply');
|
||||
$routes->addPlaceholder('embeddablePlayerTheme', '\blight|\bdark|\blight-transparent|\bdark-transparent');
|
||||
$routes->addPlaceholder(
|
||||
'uuid',
|
||||
|
@ -416,6 +416,25 @@ $routes->group(
|
|||
],
|
||||
);
|
||||
});
|
||||
|
||||
$routes->group('comments', function ($routes): void {
|
||||
$routes->post(
|
||||
'new',
|
||||
'EpisodeController::attemptCommentCreate/$1/$2',
|
||||
[
|
||||
'as' => 'comment-attempt-create',
|
||||
'filter' => 'permission:podcast-manage_publications',
|
||||
]
|
||||
);
|
||||
$routes->post(
|
||||
'delete',
|
||||
'EpisodeController::attemptCommentDelete/$1/$2',
|
||||
[
|
||||
'as' => 'comment-attempt-delete',
|
||||
'filter' => 'permission:podcast-manage_publications',
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -752,6 +771,12 @@ $routes->group('@(:podcastHandle)', function ($routes): void {
|
|||
'controller-method' => 'EpisodeController::comments/$1/$2',
|
||||
],
|
||||
]);
|
||||
$routes->get('comments/(:uuid)', 'EpisodeController::comment/$1/$2/$3', [
|
||||
'as' => 'comment',
|
||||
]);
|
||||
$routes->get('comments/(:uuid)/replies', 'EpisodeController::commentReplies/$1/$2/$3', [
|
||||
'as' => 'comment-replies',
|
||||
]);
|
||||
$routes->get('oembed.json', 'EpisodeController::oembedJSON/$1/$2', [
|
||||
'as' => 'episode-oembed-json',
|
||||
]);
|
||||
|
@ -803,73 +828,74 @@ $routes->post('interact-as-actor', 'AuthController::attemptInteractAsActor', [
|
|||
* Overwriting ActivityPub routes file
|
||||
*/
|
||||
$routes->group('@(:podcastHandle)', function ($routes): void {
|
||||
$routes->post('statuses/new', 'StatusController::attemptCreate/$1', [
|
||||
'as' => 'status-attempt-create',
|
||||
$routes->post('posts/new', 'PostController::attemptCreate/$1', [
|
||||
'as' => 'post-attempt-create',
|
||||
'filter' => 'permission:podcast-manage_publications',
|
||||
]);
|
||||
// Status
|
||||
$routes->group('statuses/(:uuid)', function ($routes): void {
|
||||
|
||||
// Post
|
||||
$routes->group('posts/(:uuid)', function ($routes): void {
|
||||
$routes->options('/', 'ActivityPubController::preflight');
|
||||
$routes->get('/', 'StatusController::view/$1/$2', [
|
||||
'as' => 'status',
|
||||
$routes->get('/', 'PostController::view/$1/$2', [
|
||||
'as' => 'post',
|
||||
'alternate-content' => [
|
||||
'application/activity+json' => [
|
||||
'namespace' => 'ActivityPub\Controllers',
|
||||
'controller-method' => 'StatusController/$2',
|
||||
'controller-method' => 'PostController/$2',
|
||||
],
|
||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
||||
'namespace' => 'ActivityPub\Controllers',
|
||||
'controller-method' => 'StatusController/$2',
|
||||
'controller-method' => 'PostController/$2',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$routes->options('replies', 'ActivityPubController::preflight');
|
||||
$routes->get('replies', 'StatusController/$1/$2', [
|
||||
'as' => 'status-replies',
|
||||
$routes->get('replies', 'PostController/$1/$2', [
|
||||
'as' => 'post-replies',
|
||||
'alternate-content' => [
|
||||
'application/activity+json' => [
|
||||
'namespace' => 'ActivityPub\Controllers',
|
||||
'controller-method' => 'StatusController::replies/$2',
|
||||
'controller-method' => 'PostController::replies/$2',
|
||||
],
|
||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams' => [
|
||||
'namespace' => 'ActivityPub\Controllers',
|
||||
'controller-method' => 'StatusController::replies/$2',
|
||||
'controller-method' => 'PostController::replies/$2',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
// Actions
|
||||
$routes->post('action', 'StatusController::attemptAction/$1/$2', [
|
||||
'as' => 'status-attempt-action',
|
||||
$routes->post('action', 'PostController::attemptAction/$1/$2', [
|
||||
'as' => 'post-attempt-action',
|
||||
'filter' => 'permission:podcast-interact_as',
|
||||
]);
|
||||
|
||||
$routes->post(
|
||||
'block-actor',
|
||||
'StatusController::attemptBlockActor/$1/$2',
|
||||
'PostController::attemptBlockActor/$1/$2',
|
||||
[
|
||||
'as' => 'status-attempt-block-actor',
|
||||
'as' => 'post-attempt-block-actor',
|
||||
'filter' => 'permission:fediverse-block_actors',
|
||||
],
|
||||
);
|
||||
$routes->post(
|
||||
'block-domain',
|
||||
'StatusController::attemptBlockDomain/$1/$2',
|
||||
'PostController::attemptBlockDomain/$1/$2',
|
||||
[
|
||||
'as' => 'status-attempt-block-domain',
|
||||
'as' => 'post-attempt-block-domain',
|
||||
'filter' => 'permission:fediverse-block_domains',
|
||||
],
|
||||
);
|
||||
$routes->post('delete', 'StatusController::attemptDelete/$1/$2', [
|
||||
'as' => 'status-attempt-delete',
|
||||
$routes->post('delete', 'PostController::attemptDelete/$1/$2', [
|
||||
'as' => 'post-attempt-delete',
|
||||
'filter' => 'permission:podcast-manage_publications',
|
||||
]);
|
||||
|
||||
$routes->get(
|
||||
'remote/(:statusAction)',
|
||||
'StatusController::remoteAction/$1/$2/$3',
|
||||
'remote/(:postAction)',
|
||||
'PostController::remoteAction/$1/$2/$3',
|
||||
[
|
||||
'as' => 'status-remote-action',
|
||||
'as' => 'post-remote-action',
|
||||
],
|
||||
);
|
||||
});
|
||||
|
|
|
@ -10,15 +10,17 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Controllers\Admin;
|
||||
|
||||
use App\Entities\Comment;
|
||||
use App\Entities\Episode;
|
||||
use App\Entities\Image;
|
||||
use App\Entities\Location;
|
||||
use App\Entities\Podcast;
|
||||
use App\Entities\Status;
|
||||
use App\Entities\Post;
|
||||
use App\Models\CommentModel;
|
||||
use App\Models\EpisodeModel;
|
||||
use App\Models\PodcastModel;
|
||||
use App\Models\PostModel;
|
||||
use App\Models\SoundbiteModel;
|
||||
use App\Models\StatusModel;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\I18n\Time;
|
||||
|
@ -429,7 +431,7 @@ class EpisodeController extends BaseController
|
|||
$db = db_connect();
|
||||
$db->transStart();
|
||||
|
||||
$newStatus = new Status([
|
||||
$newPost = new Post([
|
||||
'actor_id' => $this->podcast->actor_id,
|
||||
'episode_id' => $this->episode->id,
|
||||
'message' => $this->request->getPost('message'),
|
||||
|
@ -456,15 +458,15 @@ class EpisodeController extends BaseController
|
|||
$this->episode->published_at = Time::now();
|
||||
}
|
||||
|
||||
$newStatus->published_at = $this->episode->published_at;
|
||||
$newPost->published_at = $this->episode->published_at;
|
||||
|
||||
$statusModel = new StatusModel();
|
||||
if (! $statusModel->addStatus($newStatus)) {
|
||||
$postModel = new PostModel();
|
||||
if (! $postModel->addPost($newPost)) {
|
||||
$db->transRollback();
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $statusModel->errors());
|
||||
->with('errors', $postModel->errors());
|
||||
}
|
||||
|
||||
$episodeModel = new EpisodeModel();
|
||||
|
@ -489,7 +491,7 @@ class EpisodeController extends BaseController
|
|||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'episode' => $this->episode,
|
||||
'status' => (new StatusModel())
|
||||
'post' => (new PostModel())
|
||||
->where([
|
||||
'actor_id' => $this->podcast->actor_id,
|
||||
'episode_id' => $this->episode->id,
|
||||
|
@ -513,7 +515,7 @@ class EpisodeController extends BaseController
|
|||
public function attemptPublishEdit(): RedirectResponse
|
||||
{
|
||||
$rules = [
|
||||
'status_id' => 'required',
|
||||
'post_id' => 'required',
|
||||
'publication_method' => 'required',
|
||||
'scheduled_publication_date' =>
|
||||
'valid_date[Y-m-d H:i]|permit_empty',
|
||||
|
@ -549,19 +551,19 @@ class EpisodeController extends BaseController
|
|||
$this->episode->published_at = Time::now();
|
||||
}
|
||||
|
||||
$status = (new StatusModel())->getStatusById($this->request->getPost('status_id'));
|
||||
$post = (new PostModel())->getPostById($this->request->getPost('post_id'));
|
||||
|
||||
if ($status !== null) {
|
||||
$status->message = $this->request->getPost('message');
|
||||
$status->published_at = $this->episode->published_at;
|
||||
if ($post !== null) {
|
||||
$post->message = $this->request->getPost('message');
|
||||
$post->published_at = $this->episode->published_at;
|
||||
|
||||
$statusModel = new StatusModel();
|
||||
if (! $statusModel->editStatus($status)) {
|
||||
$postModel = new PostModel();
|
||||
if (! $postModel->editPost($post)) {
|
||||
$db->transRollback();
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $statusModel->errors());
|
||||
->with('errors', $postModel->errors());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -585,14 +587,14 @@ class EpisodeController extends BaseController
|
|||
$db = db_connect();
|
||||
$db->transStart();
|
||||
|
||||
$statusModel = new StatusModel();
|
||||
$status = $statusModel
|
||||
$postModel = new PostModel();
|
||||
$post = $postModel
|
||||
->where([
|
||||
'actor_id' => $this->podcast->actor_id,
|
||||
'episode_id' => $this->episode->id,
|
||||
])
|
||||
->first();
|
||||
$statusModel->removeStatus($status);
|
||||
$postModel->removePost($post);
|
||||
|
||||
$this->episode->published_at = null;
|
||||
|
||||
|
@ -656,13 +658,13 @@ class EpisodeController extends BaseController
|
|||
|
||||
$db->transStart();
|
||||
|
||||
$allStatusesLinkedToEpisode = (new StatusModel())
|
||||
$allPostsLinkedToEpisode = (new PostModel())
|
||||
->where([
|
||||
'episode_id' => $this->episode->id,
|
||||
])
|
||||
->findAll();
|
||||
foreach ($allStatusesLinkedToEpisode as $status) {
|
||||
(new StatusModel())->removeStatus($status);
|
||||
foreach ($allPostsLinkedToEpisode as $post) {
|
||||
(new PostModel())->removePost($post);
|
||||
}
|
||||
|
||||
// set episode published_at to null to unpublish
|
||||
|
@ -782,4 +784,41 @@ class EpisodeController extends BaseController
|
|||
]);
|
||||
return view('admin/episode/embeddable_player', $data);
|
||||
}
|
||||
|
||||
public function attemptCommentCreate(): RedirectResponse
|
||||
{
|
||||
$rules = [
|
||||
'message' => 'required|max_length[500]',
|
||||
];
|
||||
|
||||
if (! $this->validate($rules)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
$message = $this->request->getPost('message');
|
||||
|
||||
$newComment = new Comment([
|
||||
'actor_id' => interact_as_actor_id(),
|
||||
'episode_id' => $this->episode->id,
|
||||
'message' => $message,
|
||||
'created_at' => new Time('now'),
|
||||
'created_by' => user_id(),
|
||||
]);
|
||||
|
||||
$commentModel = new CommentModel();
|
||||
if (
|
||||
! $commentModel->addComment($newComment, true)
|
||||
) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $commentModel->errors());
|
||||
}
|
||||
|
||||
// Comment has been successfully created
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,10 @@ 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;
|
||||
|
@ -219,10 +221,10 @@ class EpisodeController extends BaseController
|
|||
/**
|
||||
* get comments: aggregated replies from posts referring to the episode
|
||||
*/
|
||||
$episodeComments = model('StatusModel')
|
||||
$episodeComments = model('PostModel')
|
||||
->whereIn('in_reply_to_id', function (BaseBuilder $builder): BaseBuilder {
|
||||
return $builder->select('id')
|
||||
->from('activitypub_statuses')
|
||||
->from('activitypub_posts')
|
||||
->where('episode_id', $this->episode->id);
|
||||
})
|
||||
->where('`published_at` <= NOW()', null, false)
|
||||
|
@ -254,4 +256,57 @@ 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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ use App\Libraries\PodcastActor;
|
|||
use App\Libraries\PodcastEpisode;
|
||||
use App\Models\EpisodeModel;
|
||||
use App\Models\PodcastModel;
|
||||
use App\Models\StatusModel;
|
||||
use App\Models\PostModel;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\Response;
|
||||
|
||||
|
@ -81,7 +81,7 @@ class PodcastController extends BaseController
|
|||
if (! ($cachedView = cache($cacheName))) {
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'statuses' => (new StatusModel())->getActorPublishedStatuses($this->podcast->actor_id),
|
||||
'posts' => (new PostModel())->getActorPublishedPosts($this->podcast->actor_id),
|
||||
];
|
||||
|
||||
// if user is logged in then send to the authenticated activity view
|
||||
|
|
|
@ -10,21 +10,21 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Controllers;
|
||||
|
||||
use ActivityPub\Controllers\StatusController as ActivityPubStatusController;
|
||||
use ActivityPub\Entities\Status as ActivityPubStatus;
|
||||
use ActivityPub\Controllers\PostController as ActivityPubPostController;
|
||||
use ActivityPub\Entities\Post as ActivityPubPost;
|
||||
use Analytics\AnalyticsTrait;
|
||||
use App\Entities\Actor;
|
||||
use App\Entities\Podcast;
|
||||
use App\Entities\Status as CastopodStatus;
|
||||
use App\Entities\Post as CastopodPost;
|
||||
use App\Models\EpisodeModel;
|
||||
use App\Models\PodcastModel;
|
||||
use App\Models\StatusModel;
|
||||
use App\Models\PostModel;
|
||||
use CodeIgniter\Exceptions\PageNotFoundException;
|
||||
use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\URI;
|
||||
use CodeIgniter\I18n\Time;
|
||||
|
||||
class StatusController extends ActivityPubStatusController
|
||||
class PostController extends ActivityPubPostController
|
||||
{
|
||||
use AnalyticsTrait;
|
||||
|
||||
|
@ -50,9 +50,9 @@ class StatusController extends ActivityPubStatusController
|
|||
|
||||
if (
|
||||
count($params) > 1 &&
|
||||
($status = (new StatusModel())->getStatusById($params[1])) !== null
|
||||
($post = (new PostModel())->getPostById($params[1])) !== null
|
||||
) {
|
||||
$this->status = $status;
|
||||
$this->post = $post;
|
||||
|
||||
unset($params[0]);
|
||||
unset($params[1]);
|
||||
|
@ -72,7 +72,7 @@ class StatusController extends ActivityPubStatusController
|
|||
'_',
|
||||
array_filter([
|
||||
'page',
|
||||
"status#{$this->status->id}",
|
||||
"post#{$this->post->id}",
|
||||
service('request')
|
||||
->getLocale(),
|
||||
can_user_interact() ? '_authenticated' : null,
|
||||
|
@ -83,15 +83,15 @@ class StatusController extends ActivityPubStatusController
|
|||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'actor' => $this->actor,
|
||||
'status' => $this->status,
|
||||
'post' => $this->post,
|
||||
];
|
||||
|
||||
// if user is logged in then send to the authenticated activity view
|
||||
if (can_user_interact()) {
|
||||
helper('form');
|
||||
return view('podcast/status_authenticated', $data);
|
||||
return view('podcast/post_authenticated', $data);
|
||||
}
|
||||
return view('podcast/status', $data, [
|
||||
return view('podcast/post', $data, [
|
||||
'cache' => DECADE,
|
||||
'cache_name' => $cacheName,
|
||||
]);
|
||||
|
@ -116,7 +116,7 @@ class StatusController extends ActivityPubStatusController
|
|||
|
||||
$message = $this->request->getPost('message');
|
||||
|
||||
$newStatus = new CastopodStatus([
|
||||
$newPost = new CastopodPost([
|
||||
'actor_id' => interact_as_actor_id(),
|
||||
'published_at' => Time::now(),
|
||||
'created_by' => user_id(),
|
||||
|
@ -129,23 +129,23 @@ class StatusController extends ActivityPubStatusController
|
|||
($params = extract_params_from_episode_uri(new URI($episodeUri))) &&
|
||||
($episode = (new EpisodeModel())->getEpisodeBySlug($params['podcastHandle'], $params['episodeSlug']))
|
||||
) {
|
||||
$newStatus->episode_id = $episode->id;
|
||||
$newPost->episode_id = $episode->id;
|
||||
}
|
||||
|
||||
$newStatus->message = $message;
|
||||
$newPost->message = $message;
|
||||
|
||||
$statusModel = new StatusModel();
|
||||
$postModel = new PostModel();
|
||||
if (
|
||||
! $statusModel
|
||||
->addStatus($newStatus, ! (bool) $newStatus->episode_id, true)
|
||||
! $postModel
|
||||
->addPost($newPost, ! (bool) $newPost->episode_id, true)
|
||||
) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $statusModel->errors());
|
||||
->with('errors', $postModel->errors());
|
||||
}
|
||||
|
||||
// Status has been successfully created
|
||||
// Post has been successfully created
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
|
@ -162,36 +162,36 @@ class StatusController extends ActivityPubStatusController
|
|||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
$newStatus = new ActivityPubStatus([
|
||||
$newPost = new ActivityPubPost([
|
||||
'actor_id' => interact_as_actor_id(),
|
||||
'in_reply_to_id' => $this->status->id,
|
||||
'in_reply_to_id' => $this->post->id,
|
||||
'message' => $this->request->getPost('message'),
|
||||
'published_at' => Time::now(),
|
||||
'created_by' => user_id(),
|
||||
]);
|
||||
|
||||
$statusModel = new StatusModel();
|
||||
if (! $statusModel->addReply($newStatus)) {
|
||||
$postModel = new PostModel();
|
||||
if (! $postModel->addReply($newPost)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $statusModel->errors());
|
||||
->with('errors', $postModel->errors());
|
||||
}
|
||||
|
||||
// Reply status without preview card has been successfully created
|
||||
// Reply post without preview card has been successfully created
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function attemptFavourite(): RedirectResponse
|
||||
{
|
||||
model('FavouriteModel')->toggleFavourite(interact_as_actor(), $this->status);
|
||||
model('FavouriteModel')->toggleFavourite(interact_as_actor(), $this->post);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function attemptReblog(): RedirectResponse
|
||||
{
|
||||
(new StatusModel())->toggleReblog(interact_as_actor(), $this->status);
|
||||
(new PostModel())->toggleReblog(interact_as_actor(), $this->post);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
@ -230,20 +230,20 @@ class StatusController extends ActivityPubStatusController
|
|||
|
||||
$cacheName = implode(
|
||||
'_',
|
||||
array_filter(['page', "status#{$this->status->id}", "remote_{$action}", service('request') ->getLocale()]),
|
||||
array_filter(['page', "post#{$this->post->id}", "remote_{$action}", service('request') ->getLocale()]),
|
||||
);
|
||||
|
||||
if (! ($cachedView = cache($cacheName))) {
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'actor' => $this->actor,
|
||||
'status' => $this->status,
|
||||
'post' => $this->post,
|
||||
'action' => $action,
|
||||
];
|
||||
|
||||
helper('form');
|
||||
|
||||
return view('podcast/status_remote_action', $data, [
|
||||
return view('podcast/post_remote_action', $data, [
|
||||
'cache' => DECADE,
|
||||
'cache_name' => $cacheName,
|
||||
]);
|
|
@ -38,7 +38,7 @@ class AddEpisodes extends Migration
|
|||
],
|
||||
'slug' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 191,
|
||||
'constraint' => 128,
|
||||
],
|
||||
'audio_file_path' => [
|
||||
'type' => 'VARCHAR',
|
||||
|
@ -147,17 +147,12 @@ class AddEpisodes extends Migration
|
|||
'type' => 'JSON',
|
||||
'null' => true,
|
||||
],
|
||||
'favourites_total' => [
|
||||
'posts_count' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'default' => 0,
|
||||
],
|
||||
'reblogs_total' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'default' => 0,
|
||||
],
|
||||
'statuses_total' => [
|
||||
'comments_count' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'default' => 0,
|
||||
|
|
|
@ -30,7 +30,7 @@ class AddPages extends Migration
|
|||
],
|
||||
'slug' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 191,
|
||||
'constraint' => 128,
|
||||
'unique' => true,
|
||||
],
|
||||
'content_markdown' => [
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddEpisodeIdToStatuses Adds episode_id field to activitypub_statuses table in database
|
||||
* Class AddEpisodeIdToPosts Adds episode_id field to activitypub_posts table in database
|
||||
*
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
|
@ -14,23 +14,23 @@ namespace App\Database\Migrations;
|
|||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddEpisodeIdToStatuses extends Migration
|
||||
class AddEpisodeIdToPosts extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$prefix = $this->db->getPrefix();
|
||||
|
||||
$createQuery = <<<CODE_SAMPLE
|
||||
ALTER TABLE {$prefix}activitypub_statuses
|
||||
ALTER TABLE {$prefix}activitypub_posts
|
||||
ADD COLUMN `episode_id` INT UNSIGNED NULL AFTER `replies_count`,
|
||||
ADD FOREIGN KEY {$prefix}activitypub_statuses_episode_id_foreign(episode_id) REFERENCES {$prefix}episodes(id) ON DELETE CASCADE;
|
||||
ADD FOREIGN KEY {$prefix}activitypub_posts_episode_id_foreign(episode_id) REFERENCES {$prefix}episodes(id) ON DELETE CASCADE;
|
||||
CODE_SAMPLE;
|
||||
$this->db->query($createQuery);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropForeignKey('activitypub_statuses', 'activitypub_statuses_episode_id_foreign');
|
||||
$this->forge->dropColumn('activitypub_statuses', 'episode_id');
|
||||
$this->forge->dropForeignKey('activitypub_posts', 'activitypub_posts_episode_id_foreign');
|
||||
$this->forge->dropColumn('activitypub_posts', 'episode_id');
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddCreatedByToStatuses Adds created_by field to activitypub_statuses table in database
|
||||
* Class AddCreatedByToPosts Adds created_by field to activitypub_posts table in database
|
||||
*
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
|
@ -14,23 +14,23 @@ namespace App\Database\Migrations;
|
|||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddCreatedByToStatuses extends Migration
|
||||
class AddCreatedByToPosts extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$prefix = $this->db->getPrefix();
|
||||
|
||||
$createQuery = <<<CODE_SAMPLE
|
||||
ALTER TABLE {$prefix}activitypub_statuses
|
||||
ALTER TABLE {$prefix}activitypub_posts
|
||||
ADD COLUMN `created_by` INT UNSIGNED AFTER `episode_id`,
|
||||
ADD FOREIGN KEY {$prefix}activitypub_statuses_created_by_foreign(created_by) REFERENCES {$prefix}users(id) ON DELETE CASCADE;
|
||||
ADD FOREIGN KEY {$prefix}activitypub_posts_created_by_foreign(created_by) REFERENCES {$prefix}users(id) ON DELETE CASCADE;
|
||||
CODE_SAMPLE;
|
||||
$this->db->query($createQuery);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropForeignKey('activitypub_statuses', 'activitypub_statuses_created_by_foreign');
|
||||
$this->forge->dropColumn('activitypub_statuses', 'created_by');
|
||||
$this->forge->dropForeignKey('activitypub_posts', 'activitypub_posts_created_by_foreign');
|
||||
$this->forge->dropColumn('activitypub_posts', 'created_by');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddComments creates comments table in database
|
||||
*
|
||||
* @copyright 2020 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
* @link https://castopod.org/
|
||||
*/
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddComments extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'BINARY',
|
||||
'constraint' => 16,
|
||||
],
|
||||
'uri' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 255,
|
||||
],
|
||||
'episode_id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'actor_id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'in_reply_to_id' => [
|
||||
'type' => 'BINARY',
|
||||
'constraint' => 16,
|
||||
'null' => true,
|
||||
],
|
||||
'message' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 500,
|
||||
'null' => true,
|
||||
],
|
||||
'message_html' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 600,
|
||||
'null' => true,
|
||||
],
|
||||
'likes_count' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'dislikes_count' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'replies_count' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'created_at' => [
|
||||
'type' => 'DATETIME',
|
||||
],
|
||||
'created_by' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
],
|
||||
]);
|
||||
$this->forge->addPrimaryKey('id');
|
||||
$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');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('comments');
|
||||
}
|
||||
}
|
|
@ -162,13 +162,13 @@ class AuthSeeder extends Seeder
|
|||
[
|
||||
'name' => 'manage_publications',
|
||||
'description' =>
|
||||
'Publish / unpublish episodes & statuses of a podcast',
|
||||
'Publish / unpublish episodes & posts of a podcast',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'interact_as',
|
||||
'description' =>
|
||||
'Interact as the podcast to favourite / share or reply to statuses.',
|
||||
'Interact as the podcast to favourite / share or reply to posts.',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
],
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
<?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\Entities;
|
||||
|
||||
use App\Models\EpisodeModel;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Michalsn\Uuid\UuidEntity;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @property string $id
|
||||
* @property string $uri
|
||||
* @property int $episode_id
|
||||
* @property Episode|null $episode
|
||||
* @property int $actor_id
|
||||
* @property Actor|null $actor
|
||||
* @property string $in_reply_to_id
|
||||
* @property Comment|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
|
||||
*/
|
||||
class Comment extends UuidEntity
|
||||
{
|
||||
protected ?Episode $episode = null;
|
||||
|
||||
protected ?Actor $actor = null;
|
||||
|
||||
protected ?Comment $reply_to_comment = null;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $dates = ['created_at'];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'string',
|
||||
'uri' => 'string',
|
||||
'episode_id' => 'integer',
|
||||
'actor_id' => 'integer',
|
||||
'in_reply_to_id' => '?string',
|
||||
'message' => 'string',
|
||||
'message_html' => 'string',
|
||||
'likes_count' => 'integer',
|
||||
'dislikes_count' => 'integer',
|
||||
'replies_count' => 'integer',
|
||||
'created_by' => 'integer',
|
||||
'is_from_post' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns the comment's attached episode
|
||||
*/
|
||||
public function getEpisode(): ?Episode
|
||||
{
|
||||
if ($this->episode_id === null) {
|
||||
throw new RuntimeException('Comment must have an episode_id before getting episode.');
|
||||
}
|
||||
|
||||
if (! $this->episode instanceof Episode) {
|
||||
$this->episode = (new EpisodeModel())->getEpisodeById($this->episode_id);
|
||||
}
|
||||
|
||||
return $this->episode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the comment's actor
|
||||
*/
|
||||
public function getActor(): Actor
|
||||
{
|
||||
if ($this->actor_id === null) {
|
||||
throw new RuntimeException('Comment must have an actor_id before getting actor.');
|
||||
}
|
||||
|
||||
if ($this->actor === null) {
|
||||
$this->actor = model('ActorModel', false)
|
||||
->getActorById($this->actor_id);
|
||||
}
|
||||
|
||||
return $this->actor;
|
||||
}
|
||||
|
||||
public function setMessage(string $message): static
|
||||
{
|
||||
helper('activitypub');
|
||||
|
||||
$messageWithoutTags = strip_tags($message);
|
||||
|
||||
$this->attributes['message'] = $messageWithoutTags;
|
||||
$this->attributes['message_html'] = str_replace("\n", '<br />', linkify($messageWithoutTags));
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -11,10 +11,11 @@ declare(strict_types=1);
|
|||
namespace App\Entities;
|
||||
|
||||
use App\Libraries\SimpleRSSElement;
|
||||
use App\Models\CommentModel;
|
||||
use App\Models\PersonModel;
|
||||
use App\Models\PodcastModel;
|
||||
use App\Models\PostModel;
|
||||
use App\Models\SoundbiteModel;
|
||||
use App\Models\StatusModel;
|
||||
use CodeIgniter\Entity\Entity;
|
||||
use CodeIgniter\Files\File;
|
||||
use CodeIgniter\HTTP\Files\UploadedFile;
|
||||
|
@ -65,9 +66,8 @@ use RuntimeException;
|
|||
* @property string|null $location_osm
|
||||
* @property array|null $custom_rss
|
||||
* @property string $custom_rss_string
|
||||
* @property int $favourites_total
|
||||
* @property int $reblogs_total
|
||||
* @property int $statuses_total
|
||||
* @property int $posts_count
|
||||
* @property int $comments_count
|
||||
* @property int $created_by
|
||||
* @property int $updated_by
|
||||
* @property string $publication_status;
|
||||
|
@ -117,12 +117,12 @@ class Episode extends Entity
|
|||
protected ?array $soundbites = null;
|
||||
|
||||
/**
|
||||
* @var Status[]|null
|
||||
* @var Post[]|null
|
||||
*/
|
||||
protected ?array $statuses = null;
|
||||
protected ?array $posts = null;
|
||||
|
||||
/**
|
||||
* @var Status[]|null
|
||||
* @var Comment[]|null
|
||||
*/
|
||||
protected ?array $comments = null;
|
||||
|
||||
|
@ -168,9 +168,8 @@ class Episode extends Entity
|
|||
'location_geo' => '?string',
|
||||
'location_osm' => '?string',
|
||||
'custom_rss' => '?json-array',
|
||||
'favourites_total' => 'integer',
|
||||
'reblogs_total' => 'integer',
|
||||
'statuses_total' => 'integer',
|
||||
'posts_count' => 'integer',
|
||||
'comments_count' => 'integer',
|
||||
'created_by' => 'integer',
|
||||
'updated_by' => 'integer',
|
||||
];
|
||||
|
@ -387,23 +386,23 @@ class Episode extends Entity
|
|||
}
|
||||
|
||||
/**
|
||||
* @return Status[]
|
||||
* @return Post[]
|
||||
*/
|
||||
public function getStatuses(): array
|
||||
public function getPosts(): array
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Episode must be created before getting statuses.');
|
||||
throw new RuntimeException('Episode must be created before getting posts.');
|
||||
}
|
||||
|
||||
if ($this->statuses === null) {
|
||||
$this->statuses = (new StatusModel())->getEpisodeStatuses($this->id);
|
||||
if ($this->posts === null) {
|
||||
$this->posts = (new PostModel())->getEpisodePosts($this->id);
|
||||
}
|
||||
|
||||
return $this->statuses;
|
||||
return $this->posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Status[]
|
||||
* @return Comment[]
|
||||
*/
|
||||
public function getComments(): array
|
||||
{
|
||||
|
@ -412,7 +411,7 @@ class Episode extends Entity
|
|||
}
|
||||
|
||||
if ($this->comments === null) {
|
||||
$this->comments = (new StatusModel())->getEpisodeComments($this->id);
|
||||
$this->comments = (new CommentModel())->getEpisodeComments($this->id);
|
||||
}
|
||||
|
||||
return $this->comments;
|
||||
|
@ -420,7 +419,7 @@ class Episode extends Entity
|
|||
|
||||
public function getLink(): string
|
||||
{
|
||||
return url_to('episode', $this->getPodcast()->name, $this->attributes['slug']);
|
||||
return url_to('episode', $this->getPodcast()->handle, $this->attributes['slug']);
|
||||
}
|
||||
|
||||
public function getEmbeddablePlayerUrl(string $theme = null): string
|
||||
|
|
|
@ -10,7 +10,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Entities;
|
||||
|
||||
use ActivityPub\Entities\Status as ActivityPubStatus;
|
||||
use ActivityPub\Entities\Post as ActivityPubPost;
|
||||
use App\Models\EpisodeModel;
|
||||
use RuntimeException;
|
||||
|
||||
|
@ -18,7 +18,7 @@ use RuntimeException;
|
|||
* @property int|null $episode_id
|
||||
* @property Episode|null $episode
|
||||
*/
|
||||
class Status extends ActivityPubStatus
|
||||
class Post extends ActivityPubPost
|
||||
{
|
||||
protected ?Episode $episode = null;
|
||||
|
||||
|
@ -41,12 +41,12 @@ class Status extends ActivityPubStatus
|
|||
];
|
||||
|
||||
/**
|
||||
* Returns the status' attached episode
|
||||
* Returns the post's attached episode
|
||||
*/
|
||||
public function getEpisode(): ?Episode
|
||||
{
|
||||
if ($this->episode_id === null) {
|
||||
throw new RuntimeException('Status must have an episode_id before getting episode.');
|
||||
throw new RuntimeException('Post must have an episode_id before getting episode.');
|
||||
}
|
||||
|
||||
if (! $this->episode instanceof Episode) {
|
|
@ -255,7 +255,7 @@ if (! function_exists('publication_button')) {
|
|||
/**
|
||||
* Publication button component
|
||||
*
|
||||
* Displays the appropriate publication button depending on the publication status.
|
||||
* Displays the appropriate publication button depending on the publication post.
|
||||
*/
|
||||
function publication_button(int $podcastId, int $episodeId, string $publicationStatus): string
|
||||
{
|
||||
|
|
|
@ -40,7 +40,7 @@ if (! function_exists('extract_params_from_episode_uri')) {
|
|||
function extract_params_from_episode_uri(URI $episodeUri): ?array
|
||||
{
|
||||
preg_match(
|
||||
'~@(?P<podcastHandle>[a-zA-Z0-9\_]{1,32})\/episodes\/(?P<episodeSlug>[a-zA-Z0-9\-]{1,191})~',
|
||||
'~@(?P<podcastHandle>[a-zA-Z0-9\_]{1,32})\/episodes\/(?P<episodeSlug>[a-zA-Z0-9\-]{1,128})~',
|
||||
$episodeUri->getPath(),
|
||||
$matches,
|
||||
);
|
||||
|
|
|
@ -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' => 'Write a comment...',
|
||||
'reply_to_placeholder' => 'Reply to @{actorUsername}',
|
||||
'submit' => 'Send!',
|
||||
'submit_reply' => 'Reply',
|
||||
],
|
||||
'like' => 'Like',
|
||||
'dislike' => 'Dislike',
|
||||
'replies' => '{numberOfReplies, plural,
|
||||
one {# reply}
|
||||
other {# replies}
|
||||
}',
|
||||
'block_actor' => 'Block user @{actorUsername}',
|
||||
'block_domain' => 'Block domain @{actorDomain}',
|
||||
'delete' => 'Delete comment',
|
||||
];
|
|
@ -16,19 +16,12 @@ return [
|
|||
'season_episode' => 'Season {seasonNumber} episode {episodeNumber}',
|
||||
'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}',
|
||||
'back_to_episodes' => 'Back to episodes of {podcast}',
|
||||
'comments' => 'Comments',
|
||||
'activity' => 'Activity',
|
||||
'description' => 'Description',
|
||||
'total_favourites' => '{numberOfTotalFavourites, plural,
|
||||
one {# total favourite}
|
||||
other {# total favourites}
|
||||
}',
|
||||
'total_reblogs' => '{numberOfTotalReblogs, plural,
|
||||
one {# total share}
|
||||
other {# total shares}
|
||||
}',
|
||||
'total_statuses' => '{numberOfTotalStatuses, plural,
|
||||
one {# total post}
|
||||
other {# total posts}
|
||||
'number_of_comments' => '{numberOfComments, plural,
|
||||
one {# comment}
|
||||
other {# comments}
|
||||
}',
|
||||
'all_podcast_episodes' => 'All podcast episodes',
|
||||
'back_to_podcast' => 'Go back to podcast',
|
||||
|
@ -116,14 +109,14 @@ return [
|
|||
'custom_rss_hint' => 'This will be injected within the ❬item❭ tag.',
|
||||
'block' => 'Episode should be hidden from all platforms',
|
||||
'block_hint' =>
|
||||
'The episode show or hide status. If you want this episode removed from the Apple directory, toggle this on.',
|
||||
'The episode show or hide post. If you want this episode removed from the Apple directory, toggle this on.',
|
||||
'submit_create' => 'Create episode',
|
||||
'submit_edit' => 'Save episode',
|
||||
],
|
||||
'publish_form' => [
|
||||
'back_to_episode_dashboard' => 'Back to episode dashboard',
|
||||
'status' => 'Your announcement post',
|
||||
'status_hint' =>
|
||||
'post' => 'Your announcement post',
|
||||
'post_hint' =>
|
||||
"Write a message to announce the publication of your episode. The message will be broadcasted to all your followers in the fediverse and be featured in your podcast's homepage.",
|
||||
'publication_date' => 'Publication date',
|
||||
'publication_method' => [
|
||||
|
|
|
@ -223,7 +223,7 @@ return [
|
|||
one {<span class="font-semibold">#</span> follower}
|
||||
other {<span class="font-semibold">#</span> followers}
|
||||
}',
|
||||
'statuses' => '{numberOfStatuses, plural,
|
||||
'posts' => '{numberOfPosts, plural,
|
||||
one {<span class="font-semibold">#</span> post}
|
||||
other {<span class="font-semibold">#</span> posts}
|
||||
}',
|
||||
|
|
|
@ -10,7 +10,7 @@ declare(strict_types=1);
|
|||
|
||||
return [
|
||||
'title' => "{actorDisplayName}'s post",
|
||||
'back_to_actor_statuses' => 'Back to {actor} posts',
|
||||
'back_to_actor_posts' => 'Back to {actor} posts',
|
||||
'actor_shared' => '{actor} shared',
|
||||
'reply_to' => 'Reply to @{actorUsername}',
|
||||
'form' => [
|
|
@ -16,19 +16,12 @@ return [
|
|||
'season_episode' => 'Saison {seasonNumber} épisode {episodeNumber}',
|
||||
'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}',
|
||||
'back_to_episodes' => 'Retour aux épisodes de {podcast}',
|
||||
'comments' => 'Commentaires',
|
||||
'activity' => 'Activité',
|
||||
'description' => 'Description',
|
||||
'total_favourites' => '{numberOfTotalFavourites, plural,
|
||||
one {# favori en tout}
|
||||
other {# favoris en tout}
|
||||
}',
|
||||
'total_reblogs' => '{numberOfTotalReblogs, plural,
|
||||
one {# partage en tout}
|
||||
other {# partages en tout}
|
||||
}',
|
||||
'total_statuses' => '{numberOfTotalStatuses, plural,
|
||||
one {# message}
|
||||
other {# messages}
|
||||
'number_of_comments' => '{numberOfComments, plural,
|
||||
one {# commentaire}
|
||||
other {# commentaires}
|
||||
}',
|
||||
'all_podcast_episodes' => 'Tous les épisodes du podcast',
|
||||
'back_to_podcast' => 'Revenir au podcast',
|
||||
|
@ -125,8 +118,8 @@ return [
|
|||
],
|
||||
'publish_form' => [
|
||||
'back_to_episode_dashboard' => 'Retour au tableau de bord de l’épisode',
|
||||
'status' => 'Votre message de publication',
|
||||
'status_hint' =>
|
||||
'post' => 'Votre message de publication',
|
||||
'post_hint' =>
|
||||
'Écrivez un message pour annoncer la publication de votre épisode. Le message sera diffusé à toutes les personnes qui vous suivent dans le fédiverse et mis en évidence sur la page d’accueil de votre podcast.',
|
||||
'publication_date' => 'Date de publication',
|
||||
'publication_date_clear' => 'Effacer la date de publication',
|
||||
|
|
|
@ -225,7 +225,7 @@ return [
|
|||
one {<span class="font-semibold">#</span> abonné·e}
|
||||
other {<span class="font-semibold">#</span> abonné·e·s}
|
||||
}',
|
||||
'statuses' => '{numberOfStatuses, plural,
|
||||
'posts' => '{numberOfPosts, plural,
|
||||
one {<span class="font-semibold">#</span> publication}
|
||||
other {<span class="font-semibold">#</span> publications}
|
||||
}',
|
||||
|
|
|
@ -10,7 +10,7 @@ declare(strict_types=1);
|
|||
|
||||
return [
|
||||
'title' => 'Publication de {actorDisplayName}',
|
||||
'back_to_actor_statuses' => 'Retour aux publications de {actor}',
|
||||
'back_to_actor_posts' => 'Retour aux publications de {actor}',
|
||||
'actor_shared' => '{actor} a partagé',
|
||||
'reply_to' => 'Répondre à @{actorUsername}',
|
||||
'form' => [
|
|
@ -14,19 +14,19 @@ declare(strict_types=1);
|
|||
namespace ActivityPub\Activities;
|
||||
|
||||
use ActivityPub\Core\Activity;
|
||||
use ActivityPub\Entities\Status;
|
||||
use ActivityPub\Entities\Post;
|
||||
|
||||
class AnnounceActivity extends Activity
|
||||
{
|
||||
protected string $type = 'Announce';
|
||||
|
||||
public function __construct(Status $reblogStatus)
|
||||
public function __construct(Post $reblogPost)
|
||||
{
|
||||
$this->actor = $reblogStatus->actor->uri;
|
||||
$this->object = $reblogStatus->reblog_of_status->uri;
|
||||
$this->actor = $reblogPost->actor->uri;
|
||||
$this->object = $reblogPost->reblog_of_post->uri;
|
||||
|
||||
$this->published = $reblogStatus->published_at->format(DATE_W3C);
|
||||
$this->published = $reblogPost->published_at->format(DATE_W3C);
|
||||
|
||||
$this->cc = [$reblogStatus->actor->uri, $reblogStatus->actor->followers_url];
|
||||
$this->cc = [$reblogPost->actor->uri, $reblogPost->actor->followers_url];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ $routes->addPlaceholder(
|
|||
'uuid',
|
||||
'[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}',
|
||||
);
|
||||
$routes->addPlaceholder('statusAction', '\bfavourite|\breblog|\breply');
|
||||
$routes->addPlaceholder('postAction', '\bfavourite|\breblog|\breply');
|
||||
|
||||
/**
|
||||
* ActivityPub routes file
|
||||
|
@ -54,24 +54,24 @@ $routes->group('', [
|
|||
]);
|
||||
});
|
||||
|
||||
// Status
|
||||
$routes->post('statuses/new', 'StatusController::attemptCreate/$1', [
|
||||
'as' => 'status-attempt-create',
|
||||
// Post
|
||||
$routes->post('posts/new', 'PostController::attemptCreate/$1', [
|
||||
'as' => 'post-attempt-create',
|
||||
]);
|
||||
|
||||
$routes->get('statuses/(:uuid)', 'StatusController/$1', [
|
||||
'as' => 'status',
|
||||
$routes->get('posts/(:uuid)', 'PostController/$1', [
|
||||
'as' => 'post',
|
||||
]);
|
||||
|
||||
$routes->get('statuses/(:uuid)/replies', 'StatusController/$1', [
|
||||
'as' => 'status-replies',
|
||||
$routes->get('posts/(:uuid)/replies', 'PostController/$1', [
|
||||
'as' => 'post-replies',
|
||||
]);
|
||||
|
||||
$routes->post(
|
||||
'statuses/(:uuid)/remote/(:statusAction)',
|
||||
'StatusController::attemptRemoteAction/$1/$2/$3',
|
||||
'posts/(:uuid)/remote/(:postAction)',
|
||||
'PostController::attemptRemoteAction/$1/$2/$3',
|
||||
[
|
||||
'as' => 'status-attempt-remote-action',
|
||||
'as' => 'post-attempt-remote-action',
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace ActivityPub\Controllers;
|
|||
|
||||
use ActivityPub\Config\ActivityPub;
|
||||
use ActivityPub\Entities\Actor;
|
||||
use ActivityPub\Entities\Status;
|
||||
use ActivityPub\Entities\Post;
|
||||
use ActivityPub\Objects\OrderedCollectionObject;
|
||||
use ActivityPub\Objects\OrderedCollectionPage;
|
||||
use CodeIgniter\Controller;
|
||||
|
@ -101,30 +101,30 @@ class ActorController extends Controller
|
|||
->setJSON([]);
|
||||
}
|
||||
|
||||
$replyToStatus = model('StatusModel')
|
||||
->getStatusByUri($payload->object->inReplyTo);
|
||||
$replyToPost = model('PostModel')
|
||||
->getPostByUri($payload->object->inReplyTo);
|
||||
|
||||
$reply = null;
|
||||
if ($replyToStatus !== null) {
|
||||
if ($replyToPost !== null) {
|
||||
// TODO: strip content from html to retrieve message
|
||||
// remove all html tags and reconstruct message with mentions?
|
||||
extract_text_from_html($payload->object->content);
|
||||
$reply = new Status([
|
||||
$reply = new Post([
|
||||
'uri' => $payload->object->id,
|
||||
'actor_id' => $payloadActor->id,
|
||||
'in_reply_to_id' => $replyToStatus->id,
|
||||
'in_reply_to_id' => $replyToPost->id,
|
||||
'message' => $payload->object->content,
|
||||
'published_at' => Time::parse($payload->object->published),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($reply !== null) {
|
||||
$statusId = model('StatusModel')
|
||||
$postId = model('PostModel')
|
||||
->addReply($reply, true, false);
|
||||
|
||||
model('ActivityModel')
|
||||
->update($activityId, [
|
||||
'status_id' => $statusId,
|
||||
'post_id' => $postId,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -135,12 +135,12 @@ class ActorController extends Controller
|
|||
return $this->response->setStatusCode(501)
|
||||
->setJSON([]);
|
||||
case 'Delete':
|
||||
$statusToDelete = model('StatusModel')
|
||||
->getStatusByUri($payload->object->id);
|
||||
$postToDelete = model('PostModel')
|
||||
->getPostByUri($payload->object->id);
|
||||
|
||||
if ($statusToDelete !== null) {
|
||||
model('StatusModel')
|
||||
->removeStatus($statusToDelete, false);
|
||||
if ($postToDelete !== null) {
|
||||
model('PostModel')
|
||||
->removePost($postToDelete, false);
|
||||
}
|
||||
|
||||
return $this->response->setStatusCode(200)
|
||||
|
@ -158,35 +158,35 @@ class ActorController extends Controller
|
|||
->setJSON([]);
|
||||
|
||||
case 'Like':
|
||||
// get favourited status
|
||||
$status = model('StatusModel')
|
||||
->getStatusByUri($payload->object);
|
||||
// get favourited post
|
||||
$post = model('PostModel')
|
||||
->getPostByUri($payload->object);
|
||||
|
||||
if ($status !== null) {
|
||||
if ($post !== null) {
|
||||
// Like side-effect
|
||||
model('FavouriteModel')
|
||||
->addFavourite($payloadActor, $status, false);
|
||||
->addFavourite($payloadActor, $post, false);
|
||||
|
||||
model('ActivityModel')
|
||||
->update($activityId, [
|
||||
'status_id' => $status->id,
|
||||
'post_id' => $post->id,
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->response->setStatusCode(200)
|
||||
->setJSON([]);
|
||||
case 'Announce':
|
||||
$status = model('StatusModel')
|
||||
->getStatusByUri($payload->object);
|
||||
$post = model('PostModel')
|
||||
->getPostByUri($payload->object);
|
||||
|
||||
if ($status !== null) {
|
||||
if ($post !== null) {
|
||||
model('ActivityModel')
|
||||
->update($activityId, [
|
||||
'status_id' => $status->id,
|
||||
'post_id' => $post->id,
|
||||
]);
|
||||
|
||||
model('StatusModel')
|
||||
->reblog($payloadActor, $status, false);
|
||||
model('PostModel')
|
||||
->reblog($payloadActor, $post, false);
|
||||
}
|
||||
|
||||
return $this->response->setStatusCode(200)
|
||||
|
@ -204,45 +204,45 @@ class ActorController extends Controller
|
|||
return $this->response->setStatusCode(202)
|
||||
->setJSON([]);
|
||||
case 'Like':
|
||||
$status = model('StatusModel')
|
||||
->getStatusByUri($payload->object->object);
|
||||
$post = model('PostModel')
|
||||
->getPostByUri($payload->object->object);
|
||||
|
||||
if ($status !== null) {
|
||||
if ($post !== null) {
|
||||
// revert side-effect by removing favourite from database
|
||||
model('FavouriteModel')
|
||||
->removeFavourite($payloadActor, $status, false);
|
||||
->removeFavourite($payloadActor, $post, false);
|
||||
|
||||
model('ActivityModel')
|
||||
->update($activityId, [
|
||||
'status_id' => $status->id,
|
||||
'post_id' => $post->id,
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->response->setStatusCode(200)
|
||||
->setJSON([]);
|
||||
case 'Announce':
|
||||
$status = model('StatusModel')
|
||||
->getStatusByUri($payload->object->object);
|
||||
$post = model('PostModel')
|
||||
->getPostByUri($payload->object->object);
|
||||
|
||||
$reblogStatus = null;
|
||||
if ($status !== null) {
|
||||
$reblogStatus = model('StatusModel')
|
||||
$reblogPost = null;
|
||||
if ($post !== null) {
|
||||
$reblogPost = model('PostModel')
|
||||
->where([
|
||||
'actor_id' => $payloadActor->id,
|
||||
'reblog_of_id' => service('uuid')
|
||||
->fromString($status->id)
|
||||
->fromString($post->id)
|
||||
->getBytes(),
|
||||
])
|
||||
->first();
|
||||
}
|
||||
|
||||
if ($reblogStatus !== null) {
|
||||
model('StatusModel')
|
||||
->undoReblog($reblogStatus, false);
|
||||
if ($reblogPost !== null) {
|
||||
model('PostModel')
|
||||
->undoReblog($reblogPost, false);
|
||||
|
||||
model('ActivityModel')
|
||||
->update($activityId, [
|
||||
'status_id' => $status->id,
|
||||
'post_id' => $post->id,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ declare(strict_types=1);
|
|||
namespace ActivityPub\Controllers;
|
||||
|
||||
use ActivityPub\Config\ActivityPub;
|
||||
use ActivityPub\Entities\Status;
|
||||
use ActivityPub\Entities\Post;
|
||||
use ActivityPub\Objects\OrderedCollectionObject;
|
||||
use ActivityPub\Objects\OrderedCollectionPage;
|
||||
use CodeIgniter\Controller;
|
||||
|
@ -21,14 +21,14 @@ use CodeIgniter\HTTP\Response;
|
|||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\I18n\Time;
|
||||
|
||||
class StatusController extends Controller
|
||||
class PostController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $helpers = ['activitypub'];
|
||||
|
||||
protected Status $status;
|
||||
protected Post $post;
|
||||
|
||||
protected ActivityPub $config;
|
||||
|
||||
|
@ -39,11 +39,11 @@ class StatusController extends Controller
|
|||
|
||||
public function _remap(string $method, string ...$params): mixed
|
||||
{
|
||||
if (($status = model('StatusModel')->getStatusById($params[0])) === null) {
|
||||
if (($post = model('PostModel')->getPostById($params[0])) === null) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$this->status = $status;
|
||||
$this->post = $post;
|
||||
|
||||
unset($params[0]);
|
||||
|
||||
|
@ -56,7 +56,7 @@ class StatusController extends Controller
|
|||
public function index(): Response
|
||||
{
|
||||
$noteObjectClass = $this->config->noteObject;
|
||||
$noteObject = new $noteObjectClass($this->status);
|
||||
$noteObject = new $noteObjectClass($this->post);
|
||||
|
||||
return $this->response
|
||||
->setContentType('application/activity+json')
|
||||
|
@ -69,22 +69,22 @@ class StatusController extends Controller
|
|||
public function replies(): Response
|
||||
{
|
||||
/**
|
||||
* get status replies
|
||||
* get post replies
|
||||
*/
|
||||
$statusReplies = model('StatusModel')
|
||||
->where('in_reply_to_id', service('uuid') ->fromString($this->status->id) ->getBytes())
|
||||
$postReplies = model('PostModel')
|
||||
->where('in_reply_to_id', service('uuid') ->fromString($this->post->id) ->getBytes())
|
||||
->where('`published_at` <= NOW()', null, false)
|
||||
->orderBy('published_at', 'ASC');
|
||||
|
||||
$pageNumber = (int) $this->request->getGet('page');
|
||||
|
||||
if ($pageNumber < 1) {
|
||||
$statusReplies->paginate(12);
|
||||
$pager = $statusReplies->pager;
|
||||
$postReplies->paginate(12);
|
||||
$pager = $postReplies->pager;
|
||||
$collection = new OrderedCollectionObject(null, $pager);
|
||||
} else {
|
||||
$paginatedReplies = $statusReplies->paginate(12, 'default', $pageNumber);
|
||||
$pager = $statusReplies->pager;
|
||||
$paginatedReplies = $postReplies->paginate(12, 'default', $pageNumber);
|
||||
$pager = $postReplies->pager;
|
||||
|
||||
$orderedItems = [];
|
||||
$noteObjectClass = $this->config->noteObject;
|
||||
|
@ -118,21 +118,21 @@ class StatusController extends Controller
|
|||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
$newStatus = new Status([
|
||||
$newPost = new Post([
|
||||
'actor_id' => $this->request->getPost('actor_id'),
|
||||
'message' => $this->request->getPost('message'),
|
||||
'published_at' => Time::now(),
|
||||
]);
|
||||
|
||||
if (! model('StatusModel')->addStatus($newStatus)) {
|
||||
if (! model('PostModel')->addPost($newPost)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
// TODO: translate
|
||||
->with('error', "Couldn't create Status");
|
||||
->with('error', "Couldn't create Post");
|
||||
}
|
||||
|
||||
// Status without preview card has been successfully created
|
||||
// Post without preview card has been successfully created
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
|
@ -153,7 +153,7 @@ class StatusController extends Controller
|
|||
->getActorById($this->request->getPost('actor_id'));
|
||||
|
||||
model('FavouriteModel')
|
||||
->toggleFavourite($actor, $this->status->id);
|
||||
->toggleFavourite($actor, $this->post->id);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
@ -174,8 +174,8 @@ class StatusController extends Controller
|
|||
$actor = model('ActorModel')
|
||||
->getActorById($this->request->getPost('actor_id'));
|
||||
|
||||
model('StatusModel')
|
||||
->toggleReblog($actor, $this->status);
|
||||
model('PostModel')
|
||||
->toggleReblog($actor, $this->post);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
@ -194,14 +194,14 @@ class StatusController extends Controller
|
|||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
$newReplyStatus = new Status([
|
||||
$newReplyPost = new Post([
|
||||
'actor_id' => $this->request->getPost('actor_id'),
|
||||
'in_reply_to_id' => $this->status->id,
|
||||
'in_reply_to_id' => $this->post->id,
|
||||
'message' => $this->request->getPost('message'),
|
||||
'published_at' => Time::now(),
|
||||
]);
|
||||
|
||||
if (! model('StatusModel')->addReply($newReplyStatus)) {
|
||||
if (! model('PostModel')->addReply($newReplyPost)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
|
@ -209,7 +209,7 @@ class StatusController extends Controller
|
|||
->with('error', "Couldn't create Reply");
|
||||
}
|
||||
|
||||
// Reply status without preview card has been successfully created
|
||||
// Reply post without preview card has been successfully created
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
|
@ -249,33 +249,33 @@ class StatusController extends Controller
|
|||
);
|
||||
|
||||
if (! $ostatusKey) {
|
||||
// TODO: error, couldn't remote favourite/share/reply to status
|
||||
// The instance doesn't allow its users remote actions on statuses
|
||||
// TODO: error, couldn't remote favourite/share/reply to post
|
||||
// The instance doesn't allow its users remote actions on posts
|
||||
return $this->response->setJSON([]);
|
||||
}
|
||||
|
||||
return redirect()->to(
|
||||
str_replace('{uri}', urlencode($this->status->uri), $data->links[$ostatusKey]->template),
|
||||
str_replace('{uri}', urlencode($this->post->uri), $data->links[$ostatusKey]->template),
|
||||
);
|
||||
}
|
||||
|
||||
public function attemptBlockActor(): RedirectResponse
|
||||
{
|
||||
model('ActorModel')->blockActor($this->status->actor->id);
|
||||
model('ActorModel')->blockActor($this->post->actor->id);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function attemptBlockDomain(): RedirectResponse
|
||||
{
|
||||
model('BlockedDomainModel')->blockDomain($this->status->actor->domain);
|
||||
model('BlockedDomainModel')->blockDomain($this->post->actor->domain);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function attemptDelete(): RedirectResponse
|
||||
{
|
||||
model('StatusModel', false)->removeStatus($this->status);
|
||||
model('PostModel', false)->removePost($this->post);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
|
@ -33,7 +33,7 @@ class SchedulerController extends Controller
|
|||
json_encode($scheduledActivity->payload, JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
// set activity status to delivered
|
||||
// set activity post to delivered
|
||||
model('ActivityModel')
|
||||
->update($scheduledActivity->id, [
|
||||
'task_status' => 'delivered',
|
||||
|
|
|
@ -34,7 +34,7 @@ class AddActors extends Migration
|
|||
],
|
||||
'domain' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 191,
|
||||
'constraint' => 255,
|
||||
],
|
||||
'private_key' => [
|
||||
'type' => 'TEXT',
|
||||
|
@ -93,7 +93,7 @@ class AddActors extends Migration
|
|||
'unsigned' => true,
|
||||
'default' => 0,
|
||||
],
|
||||
'statuses_count' => [
|
||||
'posts_count' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'default' => 0,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddStatuses Creates activitypub_statuses table in database
|
||||
* Class AddPosts Creates activitypub_posts table in database
|
||||
*
|
||||
* @copyright 2021 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
|
@ -14,7 +14,7 @@ namespace ActivityPub\Database\Migrations;
|
|||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddStatuses extends Migration
|
||||
class AddPosts extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
|
@ -25,7 +25,7 @@ class AddStatuses extends Migration
|
|||
],
|
||||
'uri' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 191,
|
||||
'constraint' => 255,
|
||||
],
|
||||
'actor_id' => [
|
||||
'type' => 'INT',
|
||||
|
@ -76,16 +76,16 @@ class AddStatuses extends Migration
|
|||
]);
|
||||
$this->forge->addPrimaryKey('id');
|
||||
$this->forge->addUniqueKey('uri');
|
||||
// FIXME: an actor must reblog a status only once
|
||||
// FIXME: an actor must reblog a post only once
|
||||
// $this->forge->addUniqueKey(['actor_id', 'reblog_of_id']);
|
||||
$this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('in_reply_to_id', 'activitypub_statuses', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('reblog_of_id', 'activitypub_statuses', 'id', '', 'CASCADE');
|
||||
$this->forge->createTable('activitypub_statuses');
|
||||
$this->forge->addForeignKey('in_reply_to_id', 'activitypub_posts', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('reblog_of_id', 'activitypub_posts', 'id', '', 'CASCADE');
|
||||
$this->forge->createTable('activitypub_posts');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('activitypub_statuses');
|
||||
$this->forge->dropTable('activitypub_posts');
|
||||
}
|
||||
}
|
|
@ -32,7 +32,7 @@ class AddActivities extends Migration
|
|||
'unsigned' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'status_id' => [
|
||||
'post_id' => [
|
||||
'type' => 'BINARY',
|
||||
'constraint' => 16,
|
||||
'null' => true,
|
||||
|
@ -62,7 +62,7 @@ class AddActivities extends Migration
|
|||
$this->forge->addPrimaryKey('id');
|
||||
$this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('target_actor_id', 'activitypub_actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('status_id', 'activitypub_statuses', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('post_id', 'activitypub_posts', 'id', '', 'CASCADE');
|
||||
$this->forge->createTable('activitypub_activities');
|
||||
}
|
||||
|
||||
|
|
|
@ -23,15 +23,15 @@ class AddFavourites extends Migration
|
|||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
],
|
||||
'status_id' => [
|
||||
'post_id' => [
|
||||
'type' => 'BINARY',
|
||||
'constraint' => 16,
|
||||
],
|
||||
]);
|
||||
$this->forge->addField('`created_at` timestamp NOT NULL DEFAULT current_timestamp()');
|
||||
$this->forge->addPrimaryKey(['actor_id', 'status_id']);
|
||||
$this->forge->addPrimaryKey(['actor_id', 'post_id']);
|
||||
$this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('status_id', 'activitypub_statuses', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('post_id', 'activitypub_posts', 'id', '', 'CASCADE');
|
||||
$this->forge->createTable('activitypub_favourites');
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Class AddStatusesPreviewCards Creates activitypub_statuses_preview_cards table in database
|
||||
* Class AddPostsPreviewCards Creates activitypub_posts_preview_cards table in database
|
||||
*
|
||||
* @copyright 2021 Podlibre
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
|
||||
|
@ -14,12 +14,12 @@ namespace ActivityPub\Database\Migrations;
|
|||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddStatusesPreviewCards extends Migration
|
||||
class AddPostsPreviewCards extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$this->forge->addField([
|
||||
'status_id' => [
|
||||
'post_id' => [
|
||||
'type' => 'BINARY',
|
||||
'constraint' => 16,
|
||||
],
|
||||
|
@ -29,14 +29,14 @@ class AddStatusesPreviewCards extends Migration
|
|||
],
|
||||
]);
|
||||
|
||||
$this->forge->addPrimaryKey(['status_id', 'preview_card_id']);
|
||||
$this->forge->addForeignKey('status_id', 'activitypub_statuses', 'id', '', 'CASCADE');
|
||||
$this->forge->addPrimaryKey(['post_id', 'preview_card_id']);
|
||||
$this->forge->addForeignKey('post_id', 'activitypub_posts', 'id', '', 'CASCADE');
|
||||
$this->forge->addForeignKey('preview_card_id', 'activitypub_preview_cards', 'id', '', 'CASCADE');
|
||||
$this->forge->createTable('activitypub_statuses_preview_cards');
|
||||
$this->forge->createTable('activitypub_posts_preview_cards');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->forge->dropTable('activitypub_statuses_preview_cards');
|
||||
$this->forge->dropTable('activitypub_posts_preview_cards');
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ class AddBlockedDomains extends Migration
|
|||
$this->forge->addField([
|
||||
'name' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 191,
|
||||
'constraint' => 255,
|
||||
],
|
||||
'created_at' => [
|
||||
'type' => 'DATETIME',
|
||||
|
|
|
@ -19,8 +19,8 @@ use RuntimeException;
|
|||
* @property Actor $actor
|
||||
* @property int|null $target_actor_id
|
||||
* @property Actor $target_actor
|
||||
* @property string|null $status_id
|
||||
* @property Status $status
|
||||
* @property string|null $post_id
|
||||
* @property Post $post
|
||||
* @property string $type
|
||||
* @property object $payload
|
||||
* @property string|null $task_status
|
||||
|
@ -33,12 +33,12 @@ class Activity extends UuidEntity
|
|||
|
||||
protected ?Actor $target_actor = null;
|
||||
|
||||
protected ?Status $status = null;
|
||||
protected ?Post $post = null;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $uuids = ['id', 'status_id'];
|
||||
protected $uuids = ['id', 'post_id'];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
|
@ -52,7 +52,7 @@ class Activity extends UuidEntity
|
|||
'id' => 'string',
|
||||
'actor_id' => 'integer',
|
||||
'target_actor_id' => '?integer',
|
||||
'status_id' => '?string',
|
||||
'post_id' => '?string',
|
||||
'type' => 'string',
|
||||
'payload' => 'json',
|
||||
'task_status' => '?string',
|
||||
|
@ -86,17 +86,17 @@ class Activity extends UuidEntity
|
|||
return $this->target_actor;
|
||||
}
|
||||
|
||||
public function getStatus(): Status
|
||||
public function getPost(): Post
|
||||
{
|
||||
if ($this->status_id === null) {
|
||||
throw new RuntimeException('Activity must have a status_id before getting status.');
|
||||
if ($this->post_id === null) {
|
||||
throw new RuntimeException('Activity must have a post_id before getting post.');
|
||||
}
|
||||
|
||||
if ($this->status === null) {
|
||||
$this->status = model('StatusModel', false)
|
||||
->getStatusById($this->status_id);
|
||||
if ($this->post === null) {
|
||||
$this->post = model('PostModel', false)
|
||||
->getPostById($this->post_id);
|
||||
}
|
||||
|
||||
return $this->status;
|
||||
return $this->post;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ use RuntimeException;
|
|||
* @property string|null $outbox_url
|
||||
* @property string|null $followers_url
|
||||
* @property int $followers_count
|
||||
* @property int $statuses_count
|
||||
* @property int $posts_count
|
||||
* @property bool $is_blocked
|
||||
*
|
||||
* @property Actor[] $followers
|
||||
|
@ -68,7 +68,7 @@ class Actor extends Entity
|
|||
'outbox_url' => '?string',
|
||||
'followers_url' => '?string',
|
||||
'followers_count' => 'integer',
|
||||
'statuses_count' => 'integer',
|
||||
'posts_count' => 'integer',
|
||||
'is_blocked' => 'boolean',
|
||||
];
|
||||
|
||||
|
|
|
@ -14,20 +14,20 @@ use Michalsn\Uuid\UuidEntity;
|
|||
|
||||
/**
|
||||
* @property int $actor_id
|
||||
* @property string $status_id
|
||||
* @property string $post_id
|
||||
*/
|
||||
class Favourite extends UuidEntity
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $uuids = ['status_id'];
|
||||
protected $uuids = ['post_id'];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'actor_id' => 'integer',
|
||||
'status_id' => 'string',
|
||||
'post_id' => 'string',
|
||||
];
|
||||
}
|
||||
|
|
|
@ -20,9 +20,9 @@ use RuntimeException;
|
|||
* @property int $actor_id
|
||||
* @property Actor $actor
|
||||
* @property string|null $in_reply_to_id
|
||||
* @property Status|null $reply_to_status
|
||||
* @property Post|null $reply_to_post
|
||||
* @property string|null $reblog_of_id
|
||||
* @property Status|null $reblog_of_status
|
||||
* @property Post|null $reblog_of_post
|
||||
* @property string $message
|
||||
* @property string $message_html
|
||||
* @property int $favourites_count
|
||||
|
@ -35,30 +35,30 @@ use RuntimeException;
|
|||
* @property PreviewCard|null $preview_card
|
||||
*
|
||||
* @property bool $has_replies
|
||||
* @property Status[] $replies
|
||||
* @property Status[] $reblogs
|
||||
* @property Post[] $replies
|
||||
* @property Post[] $reblogs
|
||||
*/
|
||||
class Status extends UuidEntity
|
||||
class Post extends UuidEntity
|
||||
{
|
||||
protected ?Actor $actor = null;
|
||||
|
||||
protected ?Status $reply_to_status = null;
|
||||
protected ?Post $reply_to_post = null;
|
||||
|
||||
protected ?Status $reblog_of_status = null;
|
||||
protected ?Post $reblog_of_post = null;
|
||||
|
||||
protected ?PreviewCard $preview_card = null;
|
||||
|
||||
protected bool $has_preview_card = false;
|
||||
|
||||
/**
|
||||
* @var Status[]|null
|
||||
* @var Post[]|null
|
||||
*/
|
||||
protected ?array $replies = null;
|
||||
|
||||
protected bool $has_replies = false;
|
||||
|
||||
/**
|
||||
* @var Status[]|null
|
||||
* @var Post[]|null
|
||||
*/
|
||||
protected ?array $reblogs = null;
|
||||
|
||||
|
@ -89,12 +89,12 @@ class Status extends UuidEntity
|
|||
];
|
||||
|
||||
/**
|
||||
* Returns the status's actor
|
||||
* Returns the post's actor
|
||||
*/
|
||||
public function getActor(): Actor
|
||||
{
|
||||
if ($this->actor_id === null) {
|
||||
throw new RuntimeException('Status must have an actor_id before getting actor.');
|
||||
throw new RuntimeException('Post must have an actor_id before getting actor.');
|
||||
}
|
||||
|
||||
if ($this->actor === null) {
|
||||
|
@ -108,12 +108,12 @@ class Status extends UuidEntity
|
|||
public function getPreviewCard(): ?PreviewCard
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Status must be created before getting preview_card.');
|
||||
throw new RuntimeException('Post must be created before getting preview_card.');
|
||||
}
|
||||
|
||||
if ($this->preview_card === null) {
|
||||
$this->preview_card = model('PreviewCardModel', false)
|
||||
->getStatusPreviewCard($this->id);
|
||||
->getPostPreviewCard($this->id);
|
||||
}
|
||||
|
||||
return $this->preview_card;
|
||||
|
@ -125,17 +125,17 @@ class Status extends UuidEntity
|
|||
}
|
||||
|
||||
/**
|
||||
* @return Status[]
|
||||
* @return Post[]
|
||||
*/
|
||||
public function getReplies(): array
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Status must be created before getting replies.');
|
||||
throw new RuntimeException('Post must be created before getting replies.');
|
||||
}
|
||||
|
||||
if ($this->replies === null) {
|
||||
$this->replies = (array) model('StatusModel', false)
|
||||
->getStatusReplies($this->id);
|
||||
$this->replies = (array) model('PostModel', false)
|
||||
->getPostReplies($this->id);
|
||||
}
|
||||
|
||||
return $this->replies;
|
||||
|
@ -146,49 +146,49 @@ class Status extends UuidEntity
|
|||
return $this->getReplies() !== null;
|
||||
}
|
||||
|
||||
public function getReplyToStatus(): ?self
|
||||
public function getReplyToPost(): ?self
|
||||
{
|
||||
if ($this->in_reply_to_id === null) {
|
||||
throw new RuntimeException('Status is not a reply.');
|
||||
throw new RuntimeException('Post is not a reply.');
|
||||
}
|
||||
|
||||
if ($this->reply_to_status === null) {
|
||||
$this->reply_to_status = model('StatusModel', false)
|
||||
->getStatusById($this->in_reply_to_id);
|
||||
if ($this->reply_to_post === null) {
|
||||
$this->reply_to_post = model('PostModel', false)
|
||||
->getPostById($this->in_reply_to_id);
|
||||
}
|
||||
|
||||
return $this->reply_to_status;
|
||||
return $this->reply_to_post;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Status[]
|
||||
* @return Post[]
|
||||
*/
|
||||
public function getReblogs(): array
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Status must be created before getting reblogs.');
|
||||
throw new RuntimeException('Post must be created before getting reblogs.');
|
||||
}
|
||||
|
||||
if ($this->reblogs === null) {
|
||||
$this->reblogs = (array) model('StatusModel', false)
|
||||
->getStatusReblogs($this->id);
|
||||
$this->reblogs = (array) model('PostModel', false)
|
||||
->getPostReblogs($this->id);
|
||||
}
|
||||
|
||||
return $this->reblogs;
|
||||
}
|
||||
|
||||
public function getReblogOfStatus(): ?self
|
||||
public function getReblogOfPost(): ?self
|
||||
{
|
||||
if ($this->reblog_of_id === null) {
|
||||
throw new RuntimeException('Status is not a reblog.');
|
||||
throw new RuntimeException('Post is not a reblog.');
|
||||
}
|
||||
|
||||
if ($this->reblog_of_status === null) {
|
||||
$this->reblog_of_status = model('StatusModel', false)
|
||||
->getStatusById($this->reblog_of_id);
|
||||
if ($this->reblog_of_post === null) {
|
||||
$this->reblog_of_post = model('PostModel', false)
|
||||
->getPostById($this->reblog_of_id);
|
||||
}
|
||||
|
||||
return $this->reblog_of_status;
|
||||
return $this->reblog_of_post;
|
||||
}
|
||||
|
||||
public function setMessage(string $message): static
|
|
@ -14,7 +14,7 @@ use CodeIgniter\Entity\Entity;
|
|||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $status_id
|
||||
* @property string $post_id
|
||||
* @property string $url
|
||||
* @property string $title
|
||||
* @property string $description
|
||||
|
@ -33,7 +33,7 @@ class PreviewCard extends Entity
|
|||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'status_id' => 'string',
|
||||
'post_id' => 'string',
|
||||
'url' => 'string',
|
||||
'title' => 'string',
|
||||
'description' => 'string',
|
||||
|
|
|
@ -31,7 +31,7 @@ class ActivityModel extends UuidModel
|
|||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $uuidFields = ['id', 'status_id'];
|
||||
protected $uuidFields = ['id', 'post_id'];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
|
@ -40,7 +40,7 @@ class ActivityModel extends UuidModel
|
|||
'id',
|
||||
'actor_id',
|
||||
'target_actor_id',
|
||||
'status_id',
|
||||
'post_id',
|
||||
'type',
|
||||
'payload',
|
||||
'task_status',
|
||||
|
@ -88,7 +88,7 @@ class ActivityModel extends UuidModel
|
|||
string $type,
|
||||
int $actorId,
|
||||
?int $targetActorId,
|
||||
?string $statusId,
|
||||
?string $postId,
|
||||
string $payload,
|
||||
DateTimeInterface $scheduledAt = null,
|
||||
?string $taskStatus = null
|
||||
|
@ -97,7 +97,7 @@ class ActivityModel extends UuidModel
|
|||
[
|
||||
'actor_id' => $actorId,
|
||||
'target_actor_id' => $targetActorId,
|
||||
'status_id' => $statusId,
|
||||
'post_id' => $postId,
|
||||
'type' => $type,
|
||||
'payload' => $payload,
|
||||
'scheduled_at' => $scheduledAt,
|
||||
|
|
|
@ -41,7 +41,7 @@ class ActorModel extends Model
|
|||
'outbox_url',
|
||||
'followers_url',
|
||||
'followers_count',
|
||||
'statuses_count',
|
||||
'posts_count',
|
||||
'is_blocked',
|
||||
];
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ use ActivityPub\Activities\LikeActivity;
|
|||
use ActivityPub\Activities\UndoActivity;
|
||||
use ActivityPub\Entities\Actor;
|
||||
use ActivityPub\Entities\Favourite;
|
||||
use ActivityPub\Entities\Status;
|
||||
use ActivityPub\Entities\Post;
|
||||
use CodeIgniter\Events\Events;
|
||||
use Michalsn\Uuid\UuidModel;
|
||||
|
||||
|
@ -28,12 +28,12 @@ class FavouriteModel extends UuidModel
|
|||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $uuidFields = ['status_id'];
|
||||
protected $uuidFields = ['post_id'];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $allowedFields = ['actor_id', 'status_id'];
|
||||
protected $allowedFields = ['actor_id', 'post_id'];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
|
@ -47,32 +47,32 @@ class FavouriteModel extends UuidModel
|
|||
|
||||
protected $updatedField;
|
||||
|
||||
public function addFavourite(Actor $actor, Status $status, bool $registerActivity = true): void
|
||||
public function addFavourite(Actor $actor, Post $post, bool $registerActivity = true): void
|
||||
{
|
||||
$this->db->transStart();
|
||||
|
||||
$this->insert([
|
||||
'actor_id' => $actor->id,
|
||||
'status_id' => $status->id,
|
||||
'post_id' => $post->id,
|
||||
]);
|
||||
|
||||
model('StatusModel')
|
||||
->where('id', service('uuid') ->fromString($status->id) ->getBytes())
|
||||
model('PostModel')
|
||||
->where('id', service('uuid') ->fromString($post->id) ->getBytes())
|
||||
->increment('favourites_count');
|
||||
|
||||
if ($registerActivity) {
|
||||
$likeActivity = new LikeActivity();
|
||||
$likeActivity->set('actor', $actor->uri)
|
||||
->set('object', $status->uri);
|
||||
->set('object', $post->uri);
|
||||
|
||||
$activityId = model('ActivityModel')
|
||||
->newActivity(
|
||||
'Like',
|
||||
$actor->id,
|
||||
null,
|
||||
$status->id,
|
||||
$post->id,
|
||||
$likeActivity->toJSON(),
|
||||
$status->published_at,
|
||||
$post->published_at,
|
||||
'queued',
|
||||
);
|
||||
|
||||
|
@ -84,28 +84,28 @@ class FavouriteModel extends UuidModel
|
|||
]);
|
||||
}
|
||||
|
||||
Events::trigger('on_status_favourite', $actor, $status);
|
||||
Events::trigger('on_post_favourite', $actor, $post);
|
||||
|
||||
model('StatusModel')
|
||||
->clearCache($status);
|
||||
model('PostModel')
|
||||
->clearCache($post);
|
||||
|
||||
$this->db->transComplete();
|
||||
}
|
||||
|
||||
public function removeFavourite(Actor $actor, Status $status, bool $registerActivity = true): void
|
||||
public function removeFavourite(Actor $actor, Post $post, bool $registerActivity = true): void
|
||||
{
|
||||
$this->db->transStart();
|
||||
|
||||
model('StatusModel')
|
||||
->where('id', service('uuid') ->fromString($status->id) ->getBytes())
|
||||
model('PostModel')
|
||||
->where('id', service('uuid') ->fromString($post->id) ->getBytes())
|
||||
->decrement('favourites_count');
|
||||
|
||||
$this->db
|
||||
->table('activitypub_favourites')
|
||||
->where([
|
||||
'actor_id' => $actor->id,
|
||||
'status_id' => service('uuid')
|
||||
->fromString($status->id)
|
||||
'post_id' => service('uuid')
|
||||
->fromString($post->id)
|
||||
->getBytes(),
|
||||
])
|
||||
->delete();
|
||||
|
@ -117,8 +117,8 @@ class FavouriteModel extends UuidModel
|
|||
->where([
|
||||
'type' => 'Like',
|
||||
'actor_id' => $actor->id,
|
||||
'status_id' => service('uuid')
|
||||
->fromString($status->id)
|
||||
'post_id' => service('uuid')
|
||||
->fromString($post->id)
|
||||
->getBytes(),
|
||||
])
|
||||
->first();
|
||||
|
@ -127,7 +127,7 @@ class FavouriteModel extends UuidModel
|
|||
$likeActivity
|
||||
->set('id', url_to('activity', $actor->username, $activity->id))
|
||||
->set('actor', $actor->uri)
|
||||
->set('object', $status->uri);
|
||||
->set('object', $post->uri);
|
||||
|
||||
$undoActivity
|
||||
->set('actor', $actor->uri)
|
||||
|
@ -138,9 +138,9 @@ class FavouriteModel extends UuidModel
|
|||
'Undo',
|
||||
$actor->id,
|
||||
null,
|
||||
$status->id,
|
||||
$post->id,
|
||||
$undoActivity->toJSON(),
|
||||
$status->published_at,
|
||||
$post->published_at,
|
||||
'queued',
|
||||
);
|
||||
|
||||
|
@ -152,10 +152,10 @@ class FavouriteModel extends UuidModel
|
|||
]);
|
||||
}
|
||||
|
||||
Events::trigger('on_status_undo_favourite', $actor, $status);
|
||||
Events::trigger('on_post_undo_favourite', $actor, $post);
|
||||
|
||||
model('StatusModel')
|
||||
->clearCache($status);
|
||||
model('PostModel')
|
||||
->clearCache($post);
|
||||
|
||||
$this->db->transComplete();
|
||||
}
|
||||
|
@ -163,19 +163,19 @@ class FavouriteModel extends UuidModel
|
|||
/**
|
||||
* Adds or removes favourite from database and increments count
|
||||
*/
|
||||
public function toggleFavourite(Actor $actor, Status $status): void
|
||||
public function toggleFavourite(Actor $actor, Post $post): void
|
||||
{
|
||||
if (
|
||||
$this->where([
|
||||
'actor_id' => $actor->id,
|
||||
'status_id' => service('uuid')
|
||||
->fromString($status->id)
|
||||
'post_id' => service('uuid')
|
||||
->fromString($post->id)
|
||||
->getBytes(),
|
||||
])->first()
|
||||
) {
|
||||
$this->removeFavourite($actor, $status);
|
||||
$this->removeFavourite($actor, $post);
|
||||
} else {
|
||||
$this->addFavourite($actor, $status);
|
||||
$this->addFavourite($actor, $post);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ use ActivityPub\Activities\CreateActivity;
|
|||
use ActivityPub\Activities\DeleteActivity;
|
||||
use ActivityPub\Activities\UndoActivity;
|
||||
use ActivityPub\Entities\Actor;
|
||||
use ActivityPub\Entities\Status;
|
||||
use ActivityPub\Entities\Post;
|
||||
use ActivityPub\Objects\TombstoneObject;
|
||||
use CodeIgniter\Database\BaseResult;
|
||||
use CodeIgniter\Database\Query;
|
||||
|
@ -25,12 +25,12 @@ use CodeIgniter\I18n\Time;
|
|||
use Exception;
|
||||
use Michalsn\Uuid\UuidModel;
|
||||
|
||||
class StatusModel extends UuidModel
|
||||
class PostModel extends UuidModel
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'activitypub_statuses';
|
||||
protected $table = 'activitypub_posts';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
|
@ -62,7 +62,7 @@ class StatusModel extends UuidModel
|
|||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $returnType = Status::class;
|
||||
protected $returnType = Post::class;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
|
@ -87,14 +87,14 @@ class StatusModel extends UuidModel
|
|||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $beforeInsert = ['setStatusId'];
|
||||
protected $beforeInsert = ['setPostId'];
|
||||
|
||||
public function getStatusById(string $statusId): ?Status
|
||||
public function getPostById(string $postId): ?Post
|
||||
{
|
||||
$cacheName = config('ActivityPub')
|
||||
->cachePrefix . "status#{$statusId}";
|
||||
->cachePrefix . "post#{$postId}";
|
||||
if (! ($found = cache($cacheName))) {
|
||||
$found = $this->find($statusId);
|
||||
$found = $this->find($postId);
|
||||
|
||||
cache()
|
||||
->save($cacheName, $found, DECADE);
|
||||
|
@ -103,14 +103,14 @@ class StatusModel extends UuidModel
|
|||
return $found;
|
||||
}
|
||||
|
||||
public function getStatusByUri(string $statusUri): ?Status
|
||||
public function getPostByUri(string $postUri): ?Post
|
||||
{
|
||||
$hashedStatusUri = md5($statusUri);
|
||||
$hashedPostUri = md5($postUri);
|
||||
$cacheName =
|
||||
config('ActivityPub')
|
||||
->cachePrefix . "status-{$hashedStatusUri}";
|
||||
->cachePrefix . "post-{$hashedPostUri}";
|
||||
if (! ($found = cache($cacheName))) {
|
||||
$found = $this->where('uri', $statusUri)
|
||||
$found = $this->where('uri', $postUri)
|
||||
->first();
|
||||
|
||||
cache()
|
||||
|
@ -121,16 +121,16 @@ class StatusModel extends UuidModel
|
|||
}
|
||||
|
||||
/**
|
||||
* Retrieves all published statuses for a given actor ordered by publication date
|
||||
* Retrieves all published posts for a given actor ordered by publication date
|
||||
*
|
||||
* @return Status[]
|
||||
* @return Post[]
|
||||
*/
|
||||
public function getActorPublishedStatuses(int $actorId): array
|
||||
public function getActorPublishedPosts(int $actorId): array
|
||||
{
|
||||
$cacheName =
|
||||
config('ActivityPub')
|
||||
->cachePrefix .
|
||||
"actor#{$actorId}_published_statuses";
|
||||
"actor#{$actorId}_published_posts";
|
||||
if (! ($found = cache($cacheName))) {
|
||||
$found = $this->where([
|
||||
'actor_id' => $actorId,
|
||||
|
@ -140,20 +140,20 @@ class StatusModel extends UuidModel
|
|||
->orderBy('published_at', 'DESC')
|
||||
->findAll();
|
||||
|
||||
$secondsToNextUnpublishedStatus = $this->getSecondsToNextUnpublishedStatuses($actorId);
|
||||
$secondsToNextUnpublishedPost = $this->getSecondsToNextUnpublishedPosts($actorId);
|
||||
|
||||
cache()
|
||||
->save($cacheName, $found, $secondsToNextUnpublishedStatus ? $secondsToNextUnpublishedStatus : DECADE);
|
||||
->save($cacheName, $found, $secondsToNextUnpublishedPost ? $secondsToNextUnpublishedPost : DECADE);
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timestamp difference in seconds between the next status to publish and the current timestamp. Returns
|
||||
* false if there's no status to publish
|
||||
* Returns the timestamp difference in seconds between the next post to publish and the current timestamp. Returns
|
||||
* false if there's no post to publish
|
||||
*/
|
||||
public function getSecondsToNextUnpublishedStatuses(int $actorId): int | false
|
||||
public function getSecondsToNextUnpublishedPosts(int $actorId): int | false
|
||||
{
|
||||
$result = $this->select('TIMESTAMPDIFF(SECOND, NOW(), `published_at`) as timestamp_diff')
|
||||
->where([
|
||||
|
@ -170,26 +170,26 @@ class StatusModel extends UuidModel
|
|||
}
|
||||
|
||||
/**
|
||||
* Retrieves all published replies for a given status. By default, it does not get replies from blocked actors.
|
||||
* Retrieves all published replies for a given post. By default, it does not get replies from blocked actors.
|
||||
*
|
||||
* @return Status[]
|
||||
* @return Post[]
|
||||
*/
|
||||
public function getStatusReplies(string $statusId, bool $withBlocked = false): array
|
||||
public function getPostReplies(string $postId, bool $withBlocked = false): array
|
||||
{
|
||||
$cacheName =
|
||||
config('ActivityPub')
|
||||
->cachePrefix .
|
||||
"status#{$statusId}_replies" .
|
||||
"post#{$postId}_replies" .
|
||||
($withBlocked ? '_withBlocked' : '');
|
||||
|
||||
if (! ($found = cache($cacheName))) {
|
||||
if (! $withBlocked) {
|
||||
$this->select('activitypub_statuses.*')
|
||||
->join('activitypub_actors', 'activitypub_actors.id = activitypub_statuses.actor_id', 'inner')
|
||||
$this->select('activitypub_posts.*')
|
||||
->join('activitypub_actors', 'activitypub_actors.id = activitypub_posts.actor_id', 'inner')
|
||||
->where('activitypub_actors.is_blocked', 0);
|
||||
}
|
||||
|
||||
$this->where('in_reply_to_id', $this->uuid->fromString($statusId) ->getBytes())
|
||||
$this->where('in_reply_to_id', $this->uuid->fromString($postId) ->getBytes())
|
||||
->where('`published_at` <= NOW()', null, false)
|
||||
->orderBy('published_at', 'ASC');
|
||||
$found = $this->findAll();
|
||||
|
@ -202,18 +202,18 @@ class StatusModel extends UuidModel
|
|||
}
|
||||
|
||||
/**
|
||||
* Retrieves all published reblogs for a given status
|
||||
* Retrieves all published reblogs for a given post
|
||||
*
|
||||
* @return Status[]
|
||||
* @return Post[]
|
||||
*/
|
||||
public function getStatusReblogs(string $statusId): array
|
||||
public function getPostReblogs(string $postId): array
|
||||
{
|
||||
$cacheName =
|
||||
config('ActivityPub')
|
||||
->cachePrefix . "status#{$statusId}_reblogs";
|
||||
->cachePrefix . "post#{$postId}_reblogs";
|
||||
|
||||
if (! ($found = cache($cacheName))) {
|
||||
$found = $this->where('reblog_of_id', $this->uuid->fromString($statusId) ->getBytes())
|
||||
$found = $this->where('reblog_of_id', $this->uuid->fromString($postId) ->getBytes())
|
||||
->where('`published_at` <= NOW()', null, false)
|
||||
->orderBy('published_at', 'ASC')
|
||||
->findAll();
|
||||
|
@ -225,23 +225,23 @@ class StatusModel extends UuidModel
|
|||
return $found;
|
||||
}
|
||||
|
||||
public function addPreviewCard(string $statusId, int $previewCardId): Query | bool
|
||||
public function addPreviewCard(string $postId, int $previewCardId): Query | bool
|
||||
{
|
||||
return $this->db->table('activitypub_statuses_preview_cards')
|
||||
return $this->db->table('activitypub_posts_preview_cards')
|
||||
->insert([
|
||||
'status_id' => $this->uuid->fromString($statusId)
|
||||
'post_id' => $this->uuid->fromString($postId)
|
||||
->getBytes(),
|
||||
'preview_card_id' => $previewCardId,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds status in database along preview card if relevant
|
||||
* Adds post in database along preview card if relevant
|
||||
*
|
||||
* @return string|false returns the new status id if success or false otherwise
|
||||
* @return string|false returns the new post id if success or false otherwise
|
||||
*/
|
||||
public function addStatus(
|
||||
Status $status,
|
||||
public function addPost(
|
||||
Post $post,
|
||||
bool $createPreviewCard = true,
|
||||
bool $registerActivity = true
|
||||
): string | false {
|
||||
|
@ -249,101 +249,101 @@ class StatusModel extends UuidModel
|
|||
|
||||
$this->db->transStart();
|
||||
|
||||
if (! ($newStatusId = $this->insert($status, true))) {
|
||||
if (! ($newPostId = $this->insert($post, true))) {
|
||||
$this->db->transRollback();
|
||||
|
||||
// Couldn't insert status
|
||||
// Couldn't insert post
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($createPreviewCard) {
|
||||
// parse message
|
||||
$messageUrls = extract_urls_from_message($status->message);
|
||||
$messageUrls = extract_urls_from_message($post->message);
|
||||
|
||||
if (
|
||||
$messageUrls !== [] &&
|
||||
($previewCard = get_or_create_preview_card_from_url(new URI($messageUrls[0]))) &&
|
||||
! $this->addPreviewCard($newStatusId, $previewCard->id)
|
||||
! $this->addPreviewCard($newPostId, $previewCard->id)
|
||||
) {
|
||||
$this->db->transRollback();
|
||||
// problem when linking status to preview card
|
||||
// problem when linking post to preview card
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
model('ActorModel')
|
||||
->where('id', $status->actor_id)
|
||||
->increment('statuses_count');
|
||||
model('ActorModel', false)
|
||||
->where('id', $post->actor_id)
|
||||
->increment('posts_count');
|
||||
|
||||
if ($registerActivity) {
|
||||
// set status id and uri to construct NoteObject
|
||||
$status->id = $newStatusId;
|
||||
$status->uri = url_to('status', $status->actor->username, $newStatusId);
|
||||
// set post id and uri to construct NoteObject
|
||||
$post->id = $newPostId;
|
||||
$post->uri = url_to('post', $post->actor->username, $newPostId);
|
||||
|
||||
$createActivity = new CreateActivity();
|
||||
$noteObjectClass = config('ActivityPub')
|
||||
->noteObject;
|
||||
$createActivity
|
||||
->set('actor', $status->actor->uri)
|
||||
->set('object', new $noteObjectClass($status));
|
||||
->set('actor', $post->actor->uri)
|
||||
->set('object', new $noteObjectClass($post));
|
||||
|
||||
$activityId = model('ActivityModel')
|
||||
$activityId = model('ActivityModel', false)
|
||||
->newActivity(
|
||||
'Create',
|
||||
$status->actor_id,
|
||||
$post->actor_id,
|
||||
null,
|
||||
$newStatusId,
|
||||
$newPostId,
|
||||
$createActivity->toJSON(),
|
||||
$status->published_at,
|
||||
$post->published_at,
|
||||
'queued',
|
||||
);
|
||||
|
||||
$createActivity->set('id', url_to('activity', $status->actor->username, $activityId));
|
||||
$createActivity->set('id', url_to('activity', $post->actor->username, $activityId));
|
||||
|
||||
model('ActivityModel')
|
||||
model('ActivityModel', false)
|
||||
->update($activityId, [
|
||||
'payload' => $createActivity->toJSON(),
|
||||
]);
|
||||
}
|
||||
|
||||
Events::trigger('on_status_add', $status);
|
||||
Events::trigger('on_post_add', $post);
|
||||
|
||||
$this->clearCache($status);
|
||||
$this->clearCache($post);
|
||||
|
||||
$this->db->transComplete();
|
||||
|
||||
return $newStatusId;
|
||||
return $newPostId;
|
||||
}
|
||||
|
||||
public function editStatus(Status $updatedStatus): bool
|
||||
public function editPost(Post $updatedPost): bool
|
||||
{
|
||||
$this->db->transStart();
|
||||
|
||||
// update status create activity schedule in database
|
||||
$scheduledActivity = model('ActivityModel')
|
||||
// update post create activity schedule in database
|
||||
$scheduledActivity = model('ActivityModel', false)
|
||||
->where([
|
||||
'type' => 'Create',
|
||||
'status_id' => $this->uuid
|
||||
->fromString($updatedStatus->id)
|
||||
'post_id' => $this->uuid
|
||||
->fromString($updatedPost->id)
|
||||
->getBytes(),
|
||||
])
|
||||
->first();
|
||||
|
||||
// update published date in payload
|
||||
$newPayload = $scheduledActivity->payload;
|
||||
$newPayload->object->published = $updatedStatus->published_at->format(DATE_W3C);
|
||||
model('ActivityModel')
|
||||
$newPayload->object->published = $updatedPost->published_at->format(DATE_W3C);
|
||||
model('ActivityModel', false)
|
||||
->update($scheduledActivity->id, [
|
||||
'payload' => json_encode($newPayload, JSON_THROW_ON_ERROR),
|
||||
'scheduled_at' => $updatedStatus->published_at,
|
||||
'scheduled_at' => $updatedPost->published_at,
|
||||
]);
|
||||
|
||||
// update status
|
||||
$updateResult = $this->update($updatedStatus->id, $updatedStatus);
|
||||
// update post
|
||||
$updateResult = $this->update($updatedPost->id, $updatedPost);
|
||||
|
||||
Events::trigger('on_status_edit', $updatedStatus);
|
||||
Events::trigger('on_post_edit', $updatedPost);
|
||||
|
||||
$this->clearCache($updatedStatus);
|
||||
$this->clearCache($updatedPost);
|
||||
|
||||
$this->db->transComplete();
|
||||
|
||||
|
@ -351,59 +351,59 @@ class StatusModel extends UuidModel
|
|||
}
|
||||
|
||||
/**
|
||||
* Removes a status from the database and decrements meta data
|
||||
* Removes a post from the database and decrements meta data
|
||||
*/
|
||||
public function removeStatus(Status $status, bool $registerActivity = true): BaseResult | bool
|
||||
public function removePost(Post $post, bool $registerActivity = true): BaseResult | bool
|
||||
{
|
||||
$this->db->transStart();
|
||||
|
||||
model('ActorModel')
|
||||
->where('id', $status->actor_id)
|
||||
->decrement('statuses_count');
|
||||
model('ActorModel', false)
|
||||
->where('id', $post->actor_id)
|
||||
->decrement('posts_count');
|
||||
|
||||
if ($status->in_reply_to_id !== null) {
|
||||
// Status to remove is a reply
|
||||
model('StatusModel')
|
||||
->where('id', $this->uuid->fromString($status->in_reply_to_id) ->getBytes())
|
||||
if ($post->in_reply_to_id !== null) {
|
||||
// Post to remove is a reply
|
||||
model('PostModel', false)
|
||||
->where('id', $this->uuid->fromString($post->in_reply_to_id) ->getBytes())
|
||||
->decrement('replies_count');
|
||||
|
||||
Events::trigger('on_reply_remove', $status);
|
||||
Events::trigger('on_reply_remove', $post);
|
||||
}
|
||||
|
||||
// remove all status reblogs
|
||||
foreach ($status->reblogs as $reblog) {
|
||||
// remove all post reblogs
|
||||
foreach ($post->reblogs as $reblog) {
|
||||
// FIXME: issue when actor is not local, can't get actor information
|
||||
$this->removeStatus($reblog);
|
||||
$this->removePost($reblog);
|
||||
}
|
||||
|
||||
// remove all status replies
|
||||
foreach ($status->replies as $reply) {
|
||||
$this->removeStatus($reply);
|
||||
// remove all post replies
|
||||
foreach ($post->replies as $reply) {
|
||||
$this->removePost($reply);
|
||||
}
|
||||
|
||||
// check that preview card is no longer used elsewhere before deleting it
|
||||
if (
|
||||
$status->preview_card &&
|
||||
$post->preview_card &&
|
||||
$this->db
|
||||
->table('activitypub_statuses_preview_cards')
|
||||
->where('preview_card_id', $status->preview_card->id)
|
||||
->table('activitypub_posts_preview_cards')
|
||||
->where('preview_card_id', $post->preview_card->id)
|
||||
->countAll() <= 1
|
||||
) {
|
||||
model('PreviewCardModel')->deletePreviewCard($status->preview_card->id, $status->preview_card->url);
|
||||
model('PreviewCardModel', false)->deletePreviewCard($post->preview_card->id, $post->preview_card->url);
|
||||
}
|
||||
|
||||
if ($registerActivity) {
|
||||
$deleteActivity = new DeleteActivity();
|
||||
$tombstoneObject = new TombstoneObject();
|
||||
$tombstoneObject->set('id', $status->uri);
|
||||
$tombstoneObject->set('id', $post->uri);
|
||||
$deleteActivity
|
||||
->set('actor', $status->actor->uri)
|
||||
->set('actor', $post->actor->uri)
|
||||
->set('object', $tombstoneObject);
|
||||
|
||||
$activityId = model('ActivityModel')
|
||||
$activityId = model('ActivityModel', false)
|
||||
->newActivity(
|
||||
'Delete',
|
||||
$status->actor_id,
|
||||
$post->actor_id,
|
||||
null,
|
||||
null,
|
||||
$deleteActivity->toJSON(),
|
||||
|
@ -411,20 +411,20 @@ class StatusModel extends UuidModel
|
|||
'queued',
|
||||
);
|
||||
|
||||
$deleteActivity->set('id', url_to('activity', $status->actor->username, $activityId));
|
||||
$deleteActivity->set('id', url_to('activity', $post->actor->username, $activityId));
|
||||
|
||||
model('ActivityModel')
|
||||
model('ActivityModel', false)
|
||||
->update($activityId, [
|
||||
'payload' => $deleteActivity->toJSON(),
|
||||
]);
|
||||
}
|
||||
|
||||
$result = model('StatusModel', false)
|
||||
->delete($status->id);
|
||||
$result = model('PostModel', false)
|
||||
->delete($post->id);
|
||||
|
||||
Events::trigger('on_status_remove', $status);
|
||||
Events::trigger('on_post_remove', $post);
|
||||
|
||||
$this->clearCache($status);
|
||||
$this->clearCache($post);
|
||||
|
||||
$this->db->transComplete();
|
||||
|
||||
|
@ -432,182 +432,182 @@ class StatusModel extends UuidModel
|
|||
}
|
||||
|
||||
public function addReply(
|
||||
Status $reply,
|
||||
Post $reply,
|
||||
bool $createPreviewCard = true,
|
||||
bool $registerActivity = true
|
||||
): string | false {
|
||||
if (! $reply->in_reply_to_id) {
|
||||
throw new Exception('Passed status is not a reply!');
|
||||
throw new Exception('Passed post is not a reply!');
|
||||
}
|
||||
|
||||
$this->db->transStart();
|
||||
|
||||
$statusId = $this->addStatus($reply, $createPreviewCard, $registerActivity);
|
||||
$postId = $this->addPost($reply, $createPreviewCard, $registerActivity);
|
||||
|
||||
model('StatusModel')
|
||||
model('PostModel', false)
|
||||
->where('id', $this->uuid->fromString($reply->in_reply_to_id) ->getBytes())
|
||||
->increment('replies_count');
|
||||
|
||||
Events::trigger('on_status_reply', $reply);
|
||||
Events::trigger('on_post_reply', $reply);
|
||||
|
||||
$this->clearCache($reply);
|
||||
|
||||
$this->db->transComplete();
|
||||
|
||||
return $statusId;
|
||||
return $postId;
|
||||
}
|
||||
|
||||
public function reblog(Actor $actor, Status $status, bool $registerActivity = true): string | false
|
||||
public function reblog(Actor $actor, Post $post, bool $registerActivity = true): string | false
|
||||
{
|
||||
$this->db->transStart();
|
||||
|
||||
$reblog = new Status([
|
||||
$reblog = new Post([
|
||||
'actor_id' => $actor->id,
|
||||
'reblog_of_id' => $status->id,
|
||||
'reblog_of_id' => $post->id,
|
||||
'published_at' => Time::now(),
|
||||
]);
|
||||
|
||||
// add reblog
|
||||
$reblogId = $this->insert($reblog);
|
||||
|
||||
model('ActorModel')
|
||||
model('ActorModel', false)
|
||||
->where('id', $actor->id)
|
||||
->increment('statuses_count');
|
||||
->increment('posts_count');
|
||||
|
||||
model('StatusModel')
|
||||
->where('id', $this->uuid->fromString($status->id)->getBytes())
|
||||
model('PostModel', false)
|
||||
->where('id', $this->uuid->fromString($post->id)->getBytes())
|
||||
->increment('reblogs_count');
|
||||
|
||||
if ($registerActivity) {
|
||||
$announceActivity = new AnnounceActivity($reblog);
|
||||
|
||||
$activityId = model('ActivityModel')
|
||||
$activityId = model('ActivityModel', false)
|
||||
->newActivity(
|
||||
'Announce',
|
||||
$actor->id,
|
||||
null,
|
||||
$status->id,
|
||||
$post->id,
|
||||
$announceActivity->toJSON(),
|
||||
$reblog->published_at,
|
||||
'queued',
|
||||
);
|
||||
|
||||
$announceActivity->set('id', url_to('activity', $status->actor->username, $activityId));
|
||||
$announceActivity->set('id', url_to('activity', $post->actor->username, $activityId));
|
||||
|
||||
model('ActivityModel')
|
||||
model('ActivityModel', false)
|
||||
->update($activityId, [
|
||||
'payload' => $announceActivity->toJSON(),
|
||||
]);
|
||||
}
|
||||
|
||||
Events::trigger('on_status_reblog', $actor, $status);
|
||||
Events::trigger('on_post_reblog', $actor, $post);
|
||||
|
||||
$this->clearCache($status);
|
||||
$this->clearCache($post);
|
||||
|
||||
$this->db->transComplete();
|
||||
|
||||
return $reblogId;
|
||||
}
|
||||
|
||||
public function undoReblog(Status $reblogStatus, bool $registerActivity = true): BaseResult | bool
|
||||
public function undoReblog(Post $reblogPost, bool $registerActivity = true): BaseResult | bool
|
||||
{
|
||||
$this->db->transStart();
|
||||
|
||||
model('ActorModel')
|
||||
->where('id', $reblogStatus->actor_id)
|
||||
->decrement('statuses_count');
|
||||
model('ActorModel', false)
|
||||
->where('id', $reblogPost->actor_id)
|
||||
->decrement('posts_count');
|
||||
|
||||
model('StatusModel')
|
||||
->where('id', $this->uuid->fromString($reblogStatus->reblog_of_id) ->getBytes())
|
||||
model('PostModel', false)
|
||||
->where('id', $this->uuid->fromString($reblogPost->reblog_of_id) ->getBytes())
|
||||
->decrement('reblogs_count');
|
||||
|
||||
if ($registerActivity) {
|
||||
$undoActivity = new UndoActivity();
|
||||
// get like activity
|
||||
$activity = model('ActivityModel')
|
||||
$activity = model('ActivityModel', false)
|
||||
->where([
|
||||
'type' => 'Announce',
|
||||
'actor_id' => $reblogStatus->actor_id,
|
||||
'status_id' => $this->uuid
|
||||
->fromString($reblogStatus->reblog_of_id)
|
||||
'actor_id' => $reblogPost->actor_id,
|
||||
'post_id' => $this->uuid
|
||||
->fromString($reblogPost->reblog_of_id)
|
||||
->getBytes(),
|
||||
])
|
||||
->first();
|
||||
|
||||
$announceActivity = new AnnounceActivity($reblogStatus);
|
||||
$announceActivity->set('id', url_to('activity', $reblogStatus->actor->username, $activity->id),);
|
||||
$announceActivity = new AnnounceActivity($reblogPost);
|
||||
$announceActivity->set('id', url_to('activity', $reblogPost->actor->username, $activity->id),);
|
||||
|
||||
$undoActivity
|
||||
->set('actor', $reblogStatus->actor->uri)
|
||||
->set('actor', $reblogPost->actor->uri)
|
||||
->set('object', $announceActivity);
|
||||
|
||||
$activityId = model('ActivityModel')
|
||||
$activityId = model('ActivityModel', false)
|
||||
->newActivity(
|
||||
'Undo',
|
||||
$reblogStatus->actor_id,
|
||||
$reblogPost->actor_id,
|
||||
null,
|
||||
$reblogStatus->reblog_of_id,
|
||||
$reblogPost->reblog_of_id,
|
||||
$undoActivity->toJSON(),
|
||||
Time::now(),
|
||||
'queued',
|
||||
);
|
||||
|
||||
$undoActivity->set('id', url_to('activity', $reblogStatus->actor->username, $activityId));
|
||||
$undoActivity->set('id', url_to('activity', $reblogPost->actor->username, $activityId));
|
||||
|
||||
model('ActivityModel')
|
||||
model('ActivityModel', false)
|
||||
->update($activityId, [
|
||||
'payload' => $undoActivity->toJSON(),
|
||||
]);
|
||||
}
|
||||
|
||||
$result = model('StatusModel', false)
|
||||
->delete($reblogStatus->id);
|
||||
$result = model('PostModel', false)
|
||||
->delete($reblogPost->id);
|
||||
|
||||
Events::trigger('on_status_undo_reblog', $reblogStatus);
|
||||
Events::trigger('on_post_undo_reblog', $reblogPost);
|
||||
|
||||
$this->clearCache($reblogStatus);
|
||||
$this->clearCache($reblogPost);
|
||||
|
||||
$this->db->transComplete();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function toggleReblog(Actor $actor, Status $status): void
|
||||
public function toggleReblog(Actor $actor, Post $post): void
|
||||
{
|
||||
if (
|
||||
! ($reblogStatus = $this->where([
|
||||
! ($reblogPost = $this->where([
|
||||
'actor_id' => $actor->id,
|
||||
'reblog_of_id' => $this->uuid
|
||||
->fromString($status->id)
|
||||
->fromString($post->id)
|
||||
->getBytes(),
|
||||
])->first())
|
||||
) {
|
||||
$this->reblog($actor, $status);
|
||||
$this->reblog($actor, $post);
|
||||
} else {
|
||||
$this->undoReblog($reblogStatus);
|
||||
$this->undoReblog($reblogPost);
|
||||
}
|
||||
}
|
||||
|
||||
public function clearCache(Status $status): void
|
||||
public function clearCache(Post $post): void
|
||||
{
|
||||
$cachePrefix = config('ActivityPub')
|
||||
->cachePrefix;
|
||||
|
||||
$hashedStatusUri = md5($status->uri);
|
||||
$hashedPostUri = md5($post->uri);
|
||||
|
||||
model('ActorModel')
|
||||
->clearCache($status->actor);
|
||||
model('ActorModel', false)
|
||||
->clearCache($post->actor);
|
||||
cache()
|
||||
->deleteMatching($cachePrefix . "status#{$status->id}*");
|
||||
->deleteMatching($cachePrefix . "post#{$post->id}*");
|
||||
cache()
|
||||
->deleteMatching($cachePrefix . "status-{$hashedStatusUri}*");
|
||||
->deleteMatching($cachePrefix . "post-{$hashedPostUri}*");
|
||||
|
||||
if ($status->in_reply_to_id !== null) {
|
||||
$this->clearCache($status->reply_to_status);
|
||||
if ($post->in_reply_to_id !== null) {
|
||||
$this->clearCache($post->reply_to_post);
|
||||
}
|
||||
|
||||
if ($status->reblog_of_id !== null) {
|
||||
$this->clearCache($status->reblog_of_status);
|
||||
if ($post->reblog_of_id !== null) {
|
||||
$this->clearCache($post->reblog_of_post);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -615,16 +615,16 @@ class StatusModel extends UuidModel
|
|||
* @param array<string, array<string|int, mixed>> $data
|
||||
* @return array<string, array<string|int, mixed>>
|
||||
*/
|
||||
protected function setStatusId(array $data): array
|
||||
protected function setPostId(array $data): array
|
||||
{
|
||||
$uuid4 = $this->uuid->{$this->uuidVersion}();
|
||||
$data['data']['id'] = $uuid4->toString();
|
||||
|
||||
if (! isset($data['data']['uri'])) {
|
||||
$actor = model('ActorModel')
|
||||
$actor = model('ActorModel', false)
|
||||
->getActorById((int) $data['data']['actor_id']);
|
||||
|
||||
$data['data']['uri'] = url_to('status', $actor->username, $uuid4->toString());
|
||||
$data['data']['uri'] = url_to('post', $actor->username, $uuid4->toString());
|
||||
}
|
||||
|
||||
return $data;
|
|
@ -70,18 +70,18 @@ class PreviewCardModel extends Model
|
|||
return $found;
|
||||
}
|
||||
|
||||
public function getStatusPreviewCard(string $statusId): ?PreviewCard
|
||||
public function getPostPreviewCard(string $postId): ?PreviewCard
|
||||
{
|
||||
$cacheName =
|
||||
config('ActivityPub')
|
||||
->cachePrefix . "status#{$statusId}_preview_card";
|
||||
->cachePrefix . "post#{$postId}_preview_card";
|
||||
if (! ($found = cache($cacheName))) {
|
||||
$found = $this->join(
|
||||
'activitypub_statuses_preview_cards',
|
||||
'activitypub_statuses_preview_cards.preview_card_id = id',
|
||||
'activitypub_posts_preview_cards',
|
||||
'activitypub_posts_preview_cards.preview_card_id = id',
|
||||
'inner',
|
||||
)
|
||||
->where('status_id', service('uuid') ->fromString($statusId) ->getBytes())
|
||||
->where('post_id', service('uuid') ->fromString($postId) ->getBytes())
|
||||
->first();
|
||||
|
||||
cache()
|
||||
|
|
|
@ -15,7 +15,7 @@ declare(strict_types=1);
|
|||
namespace ActivityPub\Objects;
|
||||
|
||||
use ActivityPub\Core\ObjectType;
|
||||
use ActivityPub\Entities\Status;
|
||||
use ActivityPub\Entities\Post;
|
||||
|
||||
class NoteObject extends ObjectType
|
||||
{
|
||||
|
@ -27,20 +27,20 @@ class NoteObject extends ObjectType
|
|||
|
||||
protected string $replies;
|
||||
|
||||
public function __construct(Status $status)
|
||||
public function __construct(Post $post)
|
||||
{
|
||||
$this->id = $status->uri;
|
||||
$this->id = $post->uri;
|
||||
|
||||
$this->content = $status->message_html;
|
||||
$this->published = $status->published_at->format(DATE_W3C);
|
||||
$this->attributedTo = $status->actor->uri;
|
||||
$this->content = $post->message_html;
|
||||
$this->published = $post->published_at->format(DATE_W3C);
|
||||
$this->attributedTo = $post->actor->uri;
|
||||
|
||||
if ($status->in_reply_to_id !== null) {
|
||||
$this->inReplyTo = $status->reply_to_status->uri;
|
||||
if ($post->in_reply_to_id !== null) {
|
||||
$this->inReplyTo = $post->reply_to_post->uri;
|
||||
}
|
||||
|
||||
$this->replies = url_to('status-replies', $status->actor->username, $status->id);
|
||||
$this->replies = url_to('post-replies', $post->actor->username, $post->id);
|
||||
|
||||
$this->cc = [$status->actor->followers_url];
|
||||
$this->cc = [$post->actor->followers_url];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?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\Libraries;
|
||||
|
||||
use ActivityPub\Core\ObjectType;
|
||||
use App\Entities\Comment;
|
||||
|
||||
class CommentObject extends ObjectType
|
||||
{
|
||||
protected string $type = 'Note';
|
||||
|
||||
protected string $attributedTo;
|
||||
|
||||
protected string $inReplyTo;
|
||||
|
||||
protected string $replies;
|
||||
|
||||
public function __construct(Comment $comment)
|
||||
{
|
||||
$this->id = $comment->uri;
|
||||
|
||||
$this->content = $comment->message_html;
|
||||
$this->published = $comment->created_at->format(DATE_W3C);
|
||||
$this->attributedTo = $comment->actor->uri;
|
||||
|
||||
if ($comment->in_reply_to_id !== null) {
|
||||
$this->inReplyTo = $comment->reply_to_comment->uri;
|
||||
}
|
||||
|
||||
$this->replies = url_to('comment-replies', $comment->actor->username, $comment->episode->slug, $comment->id);
|
||||
|
||||
$this->cc = [$comment->actor->followers_url];
|
||||
}
|
||||
}
|
|
@ -11,25 +11,25 @@ declare(strict_types=1);
|
|||
namespace App\Libraries;
|
||||
|
||||
use ActivityPub\Objects\NoteObject as ActivityPubNoteObject;
|
||||
use App\Entities\Status;
|
||||
use App\Entities\Post;
|
||||
|
||||
class NoteObject extends ActivityPubNoteObject
|
||||
{
|
||||
/**
|
||||
* @param Status $status
|
||||
* @param Post $post
|
||||
*/
|
||||
public function __construct(\ActivityPub\Entities\Status $status)
|
||||
public function __construct(\ActivityPub\Entities\Post $post)
|
||||
{
|
||||
parent::__construct($status);
|
||||
parent::__construct($post);
|
||||
|
||||
if ($status->episode_id) {
|
||||
if ($post->episode_id) {
|
||||
$this->content =
|
||||
'<a href="' .
|
||||
$status->episode->link .
|
||||
$post->episode->link .
|
||||
'" target="_blank" rel="noopener noreferrer">' .
|
||||
$status->episode->title .
|
||||
$post->episode->title .
|
||||
'</a><br/>' .
|
||||
$status->message_html;
|
||||
$post->message_html;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
<?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\CreateActivity;
|
||||
use App\Entities\Comment;
|
||||
use App\Libraries\CommentObject;
|
||||
use CodeIgniter\Database\BaseBuilder;
|
||||
use Michalsn\Uuid\UuidModel;
|
||||
|
||||
class CommentModel extends UuidModel
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $returnType = Comment::class;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'comments';
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $uuidFields = ['id', 'in_reply_to_id'];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $allowedFields = [
|
||||
'id',
|
||||
'uri',
|
||||
'episode_id',
|
||||
'actor_id',
|
||||
'in_reply_to_id',
|
||||
'message',
|
||||
'message_html',
|
||||
'likes_count',
|
||||
'dislikes_count',
|
||||
'replies_count',
|
||||
'created_at',
|
||||
'created_by',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $beforeInsert = ['setCommentId'];
|
||||
|
||||
public function getCommentById(string $commentId): ?Comment
|
||||
{
|
||||
$cacheName = "comment#{$commentId}";
|
||||
if (! ($found = cache($cacheName))) {
|
||||
$found = $this->find($commentId);
|
||||
|
||||
cache()
|
||||
->save($cacheName, $found, DECADE);
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
|
||||
public function addComment(Comment $comment, bool $registerActivity = false): string | false
|
||||
{
|
||||
$this->db->transStart();
|
||||
// increment Episode's comments_count
|
||||
|
||||
if (! ($newCommentId = $this->insert($comment, true))) {
|
||||
$this->db->transRollback();
|
||||
|
||||
// Couldn't insert comment
|
||||
return false;
|
||||
}
|
||||
|
||||
(new EpisodeModel())
|
||||
->where('id', $comment->episode_id)
|
||||
->increment('comments_count');
|
||||
|
||||
if ($registerActivity) {
|
||||
// set post id and uri to construct NoteObject
|
||||
$comment->id = $newCommentId;
|
||||
$comment->uri = url_to('comment', $comment->actor->username, $comment->episode->slug, $comment->id);
|
||||
|
||||
$createActivity = new CreateActivity();
|
||||
$createActivity
|
||||
->set('actor', $comment->actor->uri)
|
||||
->set('object', new CommentObject($comment));
|
||||
|
||||
$activityId = model('ActivityModel', false)
|
||||
->newActivity(
|
||||
'Create',
|
||||
$comment->actor_id,
|
||||
null,
|
||||
null,
|
||||
$createActivity->toJSON(),
|
||||
$comment->created_at,
|
||||
'queued',
|
||||
);
|
||||
|
||||
$createActivity->set('id', url_to('activity', $comment->actor->username, $activityId));
|
||||
|
||||
model('ActivityModel', false)
|
||||
->update($activityId, [
|
||||
'payload' => $createActivity->toJSON(),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->db->transComplete();
|
||||
|
||||
return $newCommentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all published posts for a given episode ordered by publication date
|
||||
*
|
||||
* @return Comment[]
|
||||
*/
|
||||
public function getEpisodeComments(int $episodeId): array
|
||||
{
|
||||
// TODO: merge with replies from posts linked to episode linked
|
||||
$episodeComments = $this->select('*, 0 as is_from_post')
|
||||
->where('episode_id', $episodeId)
|
||||
->getCompiledSelect();
|
||||
|
||||
$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'
|
||||
)
|
||||
->whereIn('in_reply_to_id', function (BaseBuilder $builder) use (&$episodeId): BaseBuilder {
|
||||
return $builder->select('id')
|
||||
->from('activitypub_posts')
|
||||
->where('episode_id', $episodeId);
|
||||
})
|
||||
->where('`created_at` <= NOW()', null, false)
|
||||
->getCompiledSelect();
|
||||
|
||||
$allEpisodeComments = $this->db->query(
|
||||
$episodeComments . ' UNION ' . $episodePostsReplies . ' ORDER BY created_at ASC'
|
||||
);
|
||||
|
||||
return $allEpisodeComments->getCustomResultObject($this->returnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all replies for a given comment
|
||||
*
|
||||
* @return Comment[]
|
||||
*/
|
||||
public function getCommentReplies(int $episodeId, string $commentId): array
|
||||
{
|
||||
// TODO: get all replies for a given comment
|
||||
return $this->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<string|int, mixed>> $data
|
||||
* @return array<string, array<string|int, mixed>>
|
||||
*/
|
||||
protected function setCommentId(array $data): array
|
||||
{
|
||||
$uuid4 = $this->uuid->{$this->uuidVersion}();
|
||||
$data['data']['id'] = $uuid4->toString();
|
||||
|
||||
if (! isset($data['data']['uri'])) {
|
||||
$actor = model('ActorModel', false)
|
||||
->getActorById((int) $data['data']['actor_id']);
|
||||
$episode = model('EpisodeModel', false)
|
||||
->find((int) $data['data']['episode_id']);
|
||||
|
||||
$data['data']['uri'] = url_to('comment', $actor->username, $episode->slug, $uuid4->toString());
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
|
@ -90,9 +90,8 @@ class EpisodeModel extends Model
|
|||
'location_geo',
|
||||
'location_osm',
|
||||
'custom_rss',
|
||||
'favourites_total',
|
||||
'reblogs_total',
|
||||
'statuses_total',
|
||||
'posts_count',
|
||||
'comments_count',
|
||||
'published_at',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
<?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\Models\PostModel as ActivityPubPostModel;
|
||||
use App\Entities\Post;
|
||||
|
||||
class PostModel extends ActivityPubPostModel
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $returnType = Post::class;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $allowedFields = [
|
||||
'id',
|
||||
'uri',
|
||||
'actor_id',
|
||||
'in_reply_to_id',
|
||||
'reblog_of_id',
|
||||
'episode_id',
|
||||
'message',
|
||||
'message_html',
|
||||
'favourites_count',
|
||||
'reblogs_count',
|
||||
'replies_count',
|
||||
'created_by',
|
||||
'published_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* Retrieves all published posts for a given episode ordered by publication date
|
||||
*
|
||||
* @return Post[]
|
||||
*/
|
||||
public function getEpisodePosts(int $episodeId): array
|
||||
{
|
||||
return $this->where([
|
||||
'episode_id' => $episodeId,
|
||||
])
|
||||
->where('`published_at` <= NOW()', null, false)
|
||||
->orderBy('published_at', 'DESC')
|
||||
->findAll();
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
<?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\Models\StatusModel as ActivityPubStatusModel;
|
||||
use App\Entities\Status;
|
||||
use CodeIgniter\Database\BaseBuilder;
|
||||
|
||||
class StatusModel extends ActivityPubStatusModel
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $returnType = Status::class;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $allowedFields = [
|
||||
'id',
|
||||
'uri',
|
||||
'actor_id',
|
||||
'in_reply_to_id',
|
||||
'reblog_of_id',
|
||||
'episode_id',
|
||||
'message',
|
||||
'message_html',
|
||||
'favourites_count',
|
||||
'reblogs_count',
|
||||
'replies_count',
|
||||
'created_by',
|
||||
'published_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* Retrieves all published statuses for a given episode ordered by publication date
|
||||
*
|
||||
* @return Status[]
|
||||
*/
|
||||
public function getEpisodeStatuses(int $episodeId): array
|
||||
{
|
||||
return $this->where([
|
||||
'episode_id' => $episodeId,
|
||||
])
|
||||
->where('`published_at` <= NOW()', null, false)
|
||||
->orderBy('published_at', 'DESC')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all published statuses for a given episode ordered by publication date
|
||||
*
|
||||
* @return Status[]
|
||||
*/
|
||||
public function getEpisodeComments(int $episodeId): array
|
||||
{
|
||||
return $this->whereIn('in_reply_to_id', function (BaseBuilder $builder) use (&$episodeId): BaseBuilder {
|
||||
return $builder->select('id')
|
||||
->from('activitypub_statuses')
|
||||
->where('episode_id', $episodeId);
|
||||
})
|
||||
->where('`published_at` <= NOW()', null, false)
|
||||
->orderBy('published_at', 'ASC')
|
||||
->findAll();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<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>
|
After Width: | Height: | Size: 393 B |
|
@ -0,0 +1,6 @@
|
|||
<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>
|
After Width: | Height: | Size: 391 B |
|
@ -7,7 +7,7 @@
|
|||
@import "./radioBtn.css";
|
||||
@import "./switch.css";
|
||||
@import "./charts.css";
|
||||
@import "./status.css";
|
||||
@import "./post.css";
|
||||
@import "./tabs.css";
|
||||
@import "./radioToggler.css";
|
||||
@import "./formInputTabs.css";
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
@layer components {
|
||||
.status-content {
|
||||
.post-content {
|
||||
& a {
|
||||
@apply text-sm font-semibold text-pine-600 hover:underline;
|
||||
}
|
||||
}
|
||||
|
||||
.status-replies > * {
|
||||
.post-replies > * {
|
||||
@apply relative;
|
||||
|
||||
& img {
|
|
@ -1,6 +1,6 @@
|
|||
@layer components {
|
||||
.tabset {
|
||||
@apply grid grid-cols-2;
|
||||
@apply grid grid-cols-3;
|
||||
}
|
||||
|
||||
.tabset > input[type="radio"] {
|
||||
|
@ -11,9 +11,10 @@
|
|||
@apply hidden;
|
||||
}
|
||||
|
||||
/* Logic for 2 tabs at most */
|
||||
/* Logic for 3 tabs at most */
|
||||
.tabset > input:first-child:checked ~ .tab-panels > .tab-panel:first-child,
|
||||
.tabset > input:nth-child(3):checked ~ .tab-panels > .tab-panel:nth-child(2) {
|
||||
.tabset > input:nth-child(3):checked ~ .tab-panels > .tab-panel:nth-child(2),
|
||||
.tabset > input:nth-child(5):checked ~ .tab-panels > .tab-panel:nth-child(3) {
|
||||
@apply block;
|
||||
}
|
||||
|
||||
|
@ -23,7 +24,7 @@
|
|||
}
|
||||
|
||||
.tabset > input:checked + label::after {
|
||||
@apply absolute inset-x-0 bottom-0 w-1/2 h-1 mx-auto bg-pine-700;
|
||||
@apply absolute inset-x-0 bottom-0 w-1/3 h-1 mx-auto bg-pine-700;
|
||||
content: "";
|
||||
}
|
||||
|
||||
|
@ -32,6 +33,6 @@
|
|||
}
|
||||
|
||||
.tabset .tab-panels {
|
||||
@apply col-span-2 p-6;
|
||||
@apply col-span-3 p-6;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
[
|
||||
'header' => lang('Episode.list.comments'),
|
||||
'cell' => function ($episode): int {
|
||||
return count($episode->comments);
|
||||
return $episode->comments_count;
|
||||
},
|
||||
],
|
||||
[
|
||||
|
|
|
@ -27,9 +27,9 @@
|
|||
|
||||
|
||||
<label for="message" class="text-lg font-semibold"><?= lang(
|
||||
'Episode.publish_form.status',
|
||||
'Episode.publish_form.post',
|
||||
) ?></label>
|
||||
<small class="max-w-md mb-2 text-gray-600"><?= lang('Episode.publish_form.status_hint') ?></small>
|
||||
<small class="max-w-md mb-2 text-gray-600"><?= lang('Episode.publish_form.post_hint') ?></small>
|
||||
<div class="mb-8 overflow-hidden bg-white shadow-md rounded-xl">
|
||||
<div class="flex px-4 py-3">
|
||||
<img src="<?= $podcast->actor->avatar_image_url ?>" alt="<?= $podcast
|
||||
|
|
|
@ -24,13 +24,13 @@
|
|||
]) ?>
|
||||
<?= csrf_field() ?>
|
||||
<?= form_hidden('client_timezone', 'UTC') ?>
|
||||
<?= form_hidden('status_id', $status->id) ?>
|
||||
<?= form_hidden('post_id', $post->id) ?>
|
||||
|
||||
|
||||
<label for="message" class="text-lg font-semibold"><?= lang(
|
||||
'Episode.publish_form.status',
|
||||
'Episode.publish_form.post',
|
||||
) ?></label>
|
||||
<small class="max-w-md mb-2 text-gray-600"><?= lang('Episode.publish_form.status_hint') ?></small>
|
||||
<small class="max-w-md mb-2 text-gray-600"><?= lang('Episode.publish_form.post_hint') ?></small>
|
||||
<div class="mb-8 overflow-hidden bg-white shadow-md rounded-xl">
|
||||
<div class="flex px-4 py-3">
|
||||
<img src="<?= $podcast->actor->avatar_image_url ?>" alt="<?= $podcast->actor
|
||||
|
@ -42,7 +42,7 @@
|
|||
<span class="text-sm text-gray-500 truncate">@<?= $podcast
|
||||
->actor->username ?></span>
|
||||
</p>
|
||||
<?= relative_time($status->published_at, 'text-xs text-gray-500') ?>
|
||||
<?= relative_time($post->published_at, 'text-xs text-gray-500') ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 mb-2">
|
||||
|
@ -54,7 +54,7 @@
|
|||
'placeholder' => 'Write your message...',
|
||||
'autofocus' => ''
|
||||
],
|
||||
old('message', $status->message, false),
|
||||
old('message', $post->message, false),
|
||||
['rows' => 2],
|
||||
) ?>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<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">
|
||||
<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>
|
||||
<?= relative_time($comment->created_at, 'text-xs text-gray-500 ml-auto') ?>
|
||||
</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>
|
||||
</div>
|
||||
</article>
|
|
@ -28,12 +28,12 @@
|
|||
<?= anchor(
|
||||
route_to('episode', $podcast->handle, $episode->slug),
|
||||
icon('chat', 'text-xl mr-1 text-gray-400') .
|
||||
$episode->statuses_total,
|
||||
$episode->comments_count,
|
||||
[
|
||||
'class' =>
|
||||
'inline-flex items-center hover:underline',
|
||||
'title' => lang('Episode.total_statuses', [
|
||||
'numberOfTotalStatuses' => $episode->statuses_total,
|
||||
'inline-flex items-center hover:underline',
|
||||
'title' => lang('Episode.number_of_comments', [
|
||||
'numberOfComments' => $episode->comments_count,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
|
|
|
@ -50,8 +50,8 @@
|
|||
<a href="<?= route_to(
|
||||
'podcast-activity',
|
||||
$podcast->handle,
|
||||
) ?>" class="hover:underline"><?= lang('Podcast.statuses', [
|
||||
'numberOfStatuses' => $podcast->actor->statuses_count,
|
||||
) ?>" class="hover:underline"><?= lang('Podcast.posts', [
|
||||
'numberOfPosts' => $podcast->actor->posts_count,
|
||||
]) ?></a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<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" />
|
||||
<div class="flex flex-col min-w-0">
|
||||
<a href="<?= $post->actor
|
||||
->uri ?>" class="flex items-baseline hover:underline" <?= $post
|
||||
->actor->is_local
|
||||
? ''
|
||||
: 'target="_blank" rel="noopener noreferrer"' ?>>
|
||||
<span class="mr-2 font-semibold truncate"><?= $post->actor
|
||||
->display_name ?></span>
|
||||
<span class="text-sm text-gray-500 truncate">@<?= $post->actor
|
||||
->username .
|
||||
($post->actor->is_local
|
||||
? ''
|
||||
: '@' . $post->actor->domain) ?></span>
|
||||
</a>
|
||||
<a href="<?= route_to('post', $podcast->handle, $post->id) ?>"
|
||||
class="text-xs text-gray-500">
|
||||
<?= relative_time($post->published_at) ?>
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
<div class="px-6 mb-4 post-content"><?= $post->message_html ?></div>
|
||||
<?php if ($post->episode_id): ?>
|
||||
<?= view('podcast/_partials/episode_preview_card', [
|
||||
'episode' => $post->episode,
|
||||
]) ?>
|
||||
<?php elseif ($post->has_preview_card): ?>
|
||||
<?= view('podcast/_partials/preview_card', [
|
||||
'preview_card' => $post->preview_card,
|
||||
]) ?>
|
||||
<?php endif; ?>
|
||||
<?= $this->include('podcast/_partials/post_actions') ?>
|
||||
</article>
|
|
@ -0,0 +1,36 @@
|
|||
<footer class="flex justify-around px-6 py-3">
|
||||
<?= anchor(
|
||||
route_to('post', $podcast->handle, $post->id),
|
||||
icon('chat', 'text-2xl mr-1 text-gray-400') . $post->replies_count,
|
||||
[
|
||||
'class' => 'inline-flex items-center hover:underline',
|
||||
'title' => lang('Post.replies', [
|
||||
'numberOfReplies' => $post->replies_count,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
<?= anchor_popup(
|
||||
route_to('post-remote-action', $podcast->handle, $post->id, 'reblog'),
|
||||
icon('repeat', 'text-2xl mr-1 text-gray-400') . $post->reblogs_count,
|
||||
[
|
||||
'class' => 'inline-flex items-center hover:underline',
|
||||
'width' => 420,
|
||||
'height' => 620,
|
||||
'title' => lang('Post.reblogs', [
|
||||
'numberOfReblogs' => $post->reblogs_count,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
<?= anchor_popup(
|
||||
route_to('post-remote-action', $podcast->handle, $post->id, 'favourite'),
|
||||
icon('heart', 'text-2xl mr-1 text-gray-400') . $post->favourites_count,
|
||||
[
|
||||
'class' => 'inline-flex items-center hover:underline',
|
||||
'width' => 420,
|
||||
'height' => 620,
|
||||
'title' => lang('Post.favourites', [
|
||||
'numberOfFavourites' => $post->favourites_count,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
</footer>
|
|
@ -1,87 +1,87 @@
|
|||
<footer class="px-6 py-3">
|
||||
<form action="<?= route_to(
|
||||
'status-attempt-action',
|
||||
'post-attempt-action',
|
||||
interact_as_actor()->username,
|
||||
$status->id,
|
||||
$post->id,
|
||||
) ?>" method="POST" class="flex justify-around">
|
||||
<?= csrf_field() ?>
|
||||
<?= anchor(
|
||||
route_to('status', $podcast->handle, $status->id),
|
||||
icon('chat', 'text-2xl mr-1 text-gray-400') . $status->replies_count,
|
||||
route_to('post', $podcast->handle, $post->id),
|
||||
icon('chat', 'text-2xl mr-1 text-gray-400') . $post->replies_count,
|
||||
[
|
||||
'class' => 'inline-flex items-center hover:underline',
|
||||
'title' => lang('Status.replies', [
|
||||
'numberOfReplies' => $status->replies_count,
|
||||
'title' => lang('Post.replies', [
|
||||
'numberOfReplies' => $post->replies_count,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
<button type="submit" name="action" value="reblog" class="inline-flex items-center hover:underline" title="<?= lang(
|
||||
'Status.reblogs',
|
||||
'Post.reblogs',
|
||||
[
|
||||
'numberOfReblogs' => $status->reblogs_count,
|
||||
'numberOfReblogs' => $post->reblogs_count,
|
||||
],
|
||||
) ?>"><?= icon('repeat', 'text-2xl mr-1 text-gray-400') .
|
||||
$status->reblogs_count ?></button>
|
||||
$post->reblogs_count ?></button>
|
||||
<button type="submit" name="action" value="favourite" class="inline-flex items-center hover:underline" title="<?= lang(
|
||||
'Status.favourites',
|
||||
'Post.favourites',
|
||||
[
|
||||
'numberOfFavourites' => $status->favourites_count,
|
||||
'numberOfFavourites' => $post->favourites_count,
|
||||
],
|
||||
) ?>"><?= icon('heart', 'text-2xl mr-1 text-gray-400') .
|
||||
$status->favourites_count ?></button>
|
||||
<button id="<?= $status->id .
|
||||
'-more-dropdown' ?>" type="button" class="px-2 py-1 text-2xl text-gray-500 outline-none focus:ring" data-dropdown="button" data-dropdown-target="<?= $status->id .
|
||||
$post->favourites_count ?></button>
|
||||
<button id="<?= $post->id .
|
||||
'-more-dropdown' ?>" type="button" class="px-2 py-1 text-2xl text-gray-500 outline-none focus:ring" data-dropdown="button" data-dropdown-target="<?= $post->id .
|
||||
'-more-dropdown-menu' ?>" aria-label="<?= lang(
|
||||
'Common.more',
|
||||
) ?>" aria-haspopup="true" aria-expanded="false"><?= icon('more') ?>
|
||||
</button>
|
||||
</form>
|
||||
<nav id="<?= $status->id .
|
||||
'-more-dropdown-menu' ?>" class="flex flex-col py-2 text-sm bg-white border rounded-lg shadow" aria-labelledby="<?= $status->id .
|
||||
<nav id="<?= $post->id .
|
||||
'-more-dropdown-menu' ?>" class="flex flex-col py-2 text-sm bg-white border rounded-lg shadow" aria-labelledby="<?= $post->id .
|
||||
'-more-dropdown' ?>" data-dropdown="menu" data-dropdown-placement="bottom">
|
||||
<?= anchor(
|
||||
route_to('status', $podcast->handle, $status->id),
|
||||
lang('Status.expand'),
|
||||
route_to('post', $podcast->handle, $post->id),
|
||||
lang('Post.expand'),
|
||||
[
|
||||
'class' => 'px-4 py-1 hover:bg-gray-100',
|
||||
],
|
||||
) ?>
|
||||
<form action="<?= route_to(
|
||||
'status-attempt-block-actor',
|
||||
'post-attempt-block-actor',
|
||||
interact_as_actor()->username,
|
||||
$status->id,
|
||||
$post->id,
|
||||
) ?>" method="POST">
|
||||
<?= csrf_field() ?>
|
||||
<button class="w-full px-4 py-1 text-left hover:bg-gray-100"><?= lang(
|
||||
'Status.block_actor',
|
||||
'Post.block_actor',
|
||||
[
|
||||
'actorUsername' => $status->actor->username,
|
||||
'actorUsername' => $post->actor->username,
|
||||
],
|
||||
) ?></button>
|
||||
</form>
|
||||
<form action="<?= route_to(
|
||||
'status-attempt-block-domain',
|
||||
'post-attempt-block-domain',
|
||||
interact_as_actor()->username,
|
||||
$status->id,
|
||||
$post->id,
|
||||
) ?>" method="POST">
|
||||
<?= csrf_field() ?>
|
||||
<button class="w-full px-4 py-1 text-left hover:bg-gray-100"><?= lang(
|
||||
'Status.block_domain',
|
||||
'Post.block_domain',
|
||||
[
|
||||
'actorDomain' => $status->actor->domain,
|
||||
'actorDomain' => $post->actor->domain,
|
||||
],
|
||||
) ?></button>
|
||||
</form>
|
||||
<?php if ($status->actor->is_local): ?>
|
||||
<?php if ($post->actor->is_local): ?>
|
||||
<hr class="my-2" />
|
||||
<form action="<?= route_to(
|
||||
'status-attempt-delete',
|
||||
$status->actor->username,
|
||||
$status->id,
|
||||
'post-attempt-delete',
|
||||
$post->actor->username,
|
||||
$post->id,
|
||||
) ?>" method="POST">
|
||||
<?= csrf_field() ?>
|
||||
<button class="w-full px-4 py-1 font-semibold text-left text-red-600 hover:bg-gray-100"><?= lang(
|
||||
'Status.delete',
|
||||
'Post.delete',
|
||||
) ?></button>
|
||||
</form>
|
||||
<?php endif; ?>
|
|
@ -0,0 +1,36 @@
|
|||
<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" />
|
||||
<div class="flex flex-col min-w-0">
|
||||
<a href="<?= $post->actor
|
||||
->uri ?>" class="flex items-baseline hover:underline" <?= $post
|
||||
->actor->is_local
|
||||
? ''
|
||||
: 'target="_blank" rel="noopener noreferrer"' ?>>
|
||||
<span class="mr-2 font-semibold truncate"><?= $post->actor
|
||||
->display_name ?></span>
|
||||
<span class="text-sm text-gray-500 truncate">@<?= $post->actor
|
||||
->username .
|
||||
($post->actor->is_local
|
||||
? ''
|
||||
: '@' . $post->actor->domain) ?></span>
|
||||
</a>
|
||||
<a href="<?= route_to('post', $podcast->handle, $post->id) ?>"
|
||||
class="text-xs text-gray-500">
|
||||
<?= relative_time($post->published_at) ?>
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
<div class="px-6 mb-4 post-content"><?= $post->message_html ?></div>
|
||||
<?php if ($post->episode_id): ?>
|
||||
<?= view('podcast/_partials/episode_preview_card', [
|
||||
'episode' => $post->episode,
|
||||
]) ?>
|
||||
<?php elseif ($post->has_preview_card): ?>
|
||||
<?= view('podcast/_partials/preview_card', [
|
||||
'preview_card' => $post->preview_card,
|
||||
]) ?>
|
||||
<?php endif; ?>
|
||||
<?= $this->include('podcast/_partials/post_actions_authenticated') ?>
|
||||
</article>
|
|
@ -1,10 +1,10 @@
|
|||
<?= $this->include('podcast/_partials/status') ?>
|
||||
<div class="-mt-2 overflow-hidden border-b border-l border-r status-replies rounded-b-xl">
|
||||
<?= $this->include('podcast/_partials/post') ?>
|
||||
<div class="-mt-2 overflow-hidden border-b border-l border-r post-replies rounded-b-xl">
|
||||
|
||||
<div class="px-6 pt-8 pb-4 bg-gray-50">
|
||||
<?= anchor_popup(
|
||||
route_to('status-remote-action', $podcast->handle, $status->id, 'reply'),
|
||||
lang('Status.reply_to', ['actorUsername' => $status->actor->username]),
|
||||
route_to('post-remote-action', $podcast->handle, $post->id, 'reply'),
|
||||
lang('Post.reply_to', ['actorUsername' => $post->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',
|
||||
|
@ -15,8 +15,8 @@
|
|||
</div>
|
||||
|
||||
|
||||
<?php if ($status->has_replies): ?>
|
||||
<?php foreach ($status->replies as $reply): ?>
|
||||
<?php if ($post->has_replies): ?>
|
||||
<?php foreach ($post->replies as $reply): ?>
|
||||
<?= view('podcast/_partials/reply', ['reply' => $reply]) ?>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
|
@ -1,7 +1,7 @@
|
|||
<?= $this->include('podcast/_partials/status_authenticated') ?>
|
||||
<div class="-mt-2 overflow-hidden border-b border-l border-r status-replies rounded-b-xl">
|
||||
<?= $this->include('podcast/_partials/post_authenticated') ?>
|
||||
<div class="-mt-2 overflow-hidden border-b border-l border-r post-replies rounded-b-xl">
|
||||
<?= form_open(
|
||||
route_to('status-attempt-action', interact_as_actor()->username, $status->id),
|
||||
route_to('post-attempt-action', interact_as_actor()->username, $post->id),
|
||||
[
|
||||
'class' => 'bg-gray-50 flex px-6 pt-8 pb-4',
|
||||
],
|
||||
|
@ -16,8 +16,8 @@
|
|||
'name' => 'message',
|
||||
'class' => 'form-textarea mb-4 w-full',
|
||||
'required' => 'required',
|
||||
'placeholder' => lang('Status.form.reply_to_placeholder', [
|
||||
'actorUsername' => $status->actor->username,
|
||||
'placeholder' => lang('Post.form.reply_to_placeholder', [
|
||||
'actorUsername' => $post->actor->username,
|
||||
]),
|
||||
],
|
||||
old('message', '', false),
|
||||
|
@ -26,7 +26,7 @@
|
|||
],
|
||||
) ?>
|
||||
<?= button(
|
||||
lang('Status.form.submit_reply'),
|
||||
lang('Post.form.submit_reply'),
|
||||
'',
|
||||
['variant' => 'primary', 'size' => 'small'],
|
||||
[
|
||||
|
@ -39,8 +39,8 @@
|
|||
</div>
|
||||
<?= form_close() ?>
|
||||
|
||||
<?php if ($status->has_replies): ?>
|
||||
<?php foreach ($status->replies as $reply): ?>
|
||||
<?php if ($post->has_replies): ?>
|
||||
<?php foreach ($post->replies as $reply): ?>
|
||||
<?= view('podcast/_partials/reply_authenticated', [
|
||||
'reply' => $reply,
|
||||
]) ?>
|
|
@ -1,43 +1,43 @@
|
|||
<article class="relative z-10 w-full bg-white shadow-md rounded-2xl">
|
||||
<article class="relative z-10 w-full bg-white shadow rounded-2xl">
|
||||
<p class="inline-flex px-6 pt-4 text-xs text-gray-700"><?= icon(
|
||||
'repeat',
|
||||
'text-lg mr-2 text-gray-400',
|
||||
) .
|
||||
lang('Status.actor_shared', [
|
||||
'actor' => $status->actor->display_name,
|
||||
lang('Post.actor_shared', [
|
||||
'actor' => $post->actor->display_name,
|
||||
]) ?></p>
|
||||
<header class="flex px-6 py-4">
|
||||
<img src="<?= $status->actor
|
||||
->avatar_image_url ?>" alt="<?= $status->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
|
||||
<img src="<?= $post->actor
|
||||
->avatar_image_url ?>" alt="<?= $post->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
|
||||
<div class="flex flex-col min-w-0">
|
||||
<a href="<?= $status->actor
|
||||
->uri ?>" class="flex items-baseline hover:underline" <?= $status
|
||||
<a href="<?= $post->actor
|
||||
->uri ?>" class="flex items-baseline hover:underline" <?= $post
|
||||
->actor->is_local
|
||||
? ''
|
||||
: 'target="_blank" rel="noopener noreferrer"' ?>>
|
||||
<span class="mr-2 font-semibold truncate"><?= $status->actor
|
||||
<span class="mr-2 font-semibold truncate"><?= $post->actor
|
||||
->display_name ?></span>
|
||||
<span class="text-sm text-gray-500 truncate">@<?= $status->actor
|
||||
<span class="text-sm text-gray-500 truncate">@<?= $post->actor
|
||||
->username .
|
||||
($status->actor->is_local
|
||||
($post->actor->is_local
|
||||
? ''
|
||||
: '@' . $status->actor->domain) ?></span>
|
||||
: '@' . $post->actor->domain) ?></span>
|
||||
</a>
|
||||
<a href="<?= route_to('status', $podcast->handle, $status->id) ?>"
|
||||
<a href="<?= route_to('post', $podcast->handle, $post->id) ?>"
|
||||
class="text-xs text-gray-500">
|
||||
<?= relative_time($status->published_at) ?>
|
||||
<?= relative_time($post->published_at) ?>
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
<div class="px-6 mb-4 status-content"><?= $status->message_html ?></div>
|
||||
<?php if ($status->episode_id): ?>
|
||||
<div class="px-6 mb-4 post-content"><?= $post->message_html ?></div>
|
||||
<?php if ($post->episode_id): ?>
|
||||
<?= view('podcast/_partials/episode_preview_card', [
|
||||
'episode' => $status->episode,
|
||||
'episode' => $post->episode,
|
||||
]) ?>
|
||||
<?php elseif ($status->has_preview_card): ?>
|
||||
<?php elseif ($post->has_preview_card): ?>
|
||||
<?= view('podcast/_partials/preview_card', [
|
||||
'preview_card' => $status->preview_card,
|
||||
'preview_card' => $post->preview_card,
|
||||
]) ?>
|
||||
<?php endif; ?>
|
||||
<?= $this->include('podcast/_partials/status_actions') ?>
|
||||
<?= $this->include('podcast/_partials/post_actions') ?>
|
||||
</article>
|
||||
|
|
|
@ -1,43 +1,43 @@
|
|||
<article class="relative z-10 w-full bg-white shadow-md rounded-2xl">
|
||||
<article class="relative z-10 w-full bg-white shadow rounded-2xl">
|
||||
<p class="inline-flex px-6 pt-4 text-xs text-gray-700"><?= icon(
|
||||
'repeat',
|
||||
'text-lg mr-2 text-gray-400',
|
||||
) .
|
||||
lang('Status.actor_shared', [
|
||||
'actor' => $status->actor->display_name,
|
||||
lang('Post.actor_shared', [
|
||||
'actor' => $post->actor->display_name,
|
||||
]) ?></p>
|
||||
<header class="flex px-6 py-4">
|
||||
<img src="<?= $status->actor
|
||||
->avatar_image_url ?>" alt="<?= $status->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
|
||||
<img src="<?= $post->actor
|
||||
->avatar_image_url ?>" alt="<?= $post->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
|
||||
<div class="flex flex-col min-w-0">
|
||||
<a href="<?= $status->actor
|
||||
->uri ?>" class="flex items-baseline hover:underline" <?= $status
|
||||
<a href="<?= $post->actor
|
||||
->uri ?>" class="flex items-baseline hover:underline" <?= $post
|
||||
->actor->is_local
|
||||
? ''
|
||||
: 'target="_blank" rel="noopener noreferrer"' ?>>
|
||||
<span class="mr-2 font-semibold truncate"><?= $status->actor
|
||||
<span class="mr-2 font-semibold truncate"><?= $post->actor
|
||||
->display_name ?></span>
|
||||
<span class="text-sm text-gray-500 truncate">@<?= $status->actor
|
||||
<span class="text-sm text-gray-500 truncate">@<?= $post->actor
|
||||
->username .
|
||||
($status->actor->is_local
|
||||
($post->actor->is_local
|
||||
? ''
|
||||
: '@' . $status->actor->domain) ?></span>
|
||||
: '@' . $post->actor->domain) ?></span>
|
||||
</a>
|
||||
<a href="<?= route_to('status', $podcast->handle, $status->id) ?>"
|
||||
<a href="<?= route_to('post', $podcast->handle, $post->id) ?>"
|
||||
class="text-xs text-gray-500">
|
||||
<?= relative_time($status->published_at) ?>
|
||||
<?= relative_time($post->published_at) ?>
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
<div class="px-6 mb-4 status-content"><?= $status->message_html ?></div>
|
||||
<?php if ($status->episode_id): ?>
|
||||
<div class="px-6 mb-4 post-content"><?= $post->message_html ?></div>
|
||||
<?php if ($post->episode_id): ?>
|
||||
<?= view('podcast/_partials/episode_preview_card', [
|
||||
'episode' => $status->episode,
|
||||
'episode' => $post->episode,
|
||||
]) ?>
|
||||
<?php elseif ($status->has_preview_card): ?>
|
||||
<?php elseif ($post->has_preview_card): ?>
|
||||
<?= view('podcast/_partials/preview_card', [
|
||||
'preview_card' => $status->preview_card,
|
||||
'preview_card' => $post->preview_card,
|
||||
]) ?>
|
||||
<?php endif; ?>
|
||||
<?= $this->include('podcast/_partials/status_actions_authenticated') ?>
|
||||
<?= $this->include('podcast/_partials/post_actions_authenticated') ?>
|
||||
</article>
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
->display_name ?><span class="ml-1 text-sm font-normal text-gray-600">@<?= $reply
|
||||
->actor->username .
|
||||
($reply->actor->is_local ? '' : '@' . $reply->actor->domain) ?></span></a>
|
||||
<?= relative_time($status->published_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?>
|
||||
<?= relative_time($post->published_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?>
|
||||
</header>
|
||||
<p class="mb-2 status-content"><?= $reply->message_html ?></p>
|
||||
<p class="mb-2 post-content"><?= $reply->message_html ?></p>
|
||||
<?php if ($reply->has_preview_card): ?>
|
||||
<?= view('podcast/_partials/preview_card', [
|
||||
'preview_card' => $reply->preview_card,
|
||||
|
|
|
@ -1,34 +1,34 @@
|
|||
<footer class="mt-2 space-x-6 text-sm">
|
||||
<?= anchor(
|
||||
route_to('status', $podcast->handle, $reply->id),
|
||||
route_to('post', $podcast->handle, $reply->id),
|
||||
icon('chat', 'text-xl mr-1 text-gray-400') . $reply->replies_count,
|
||||
[
|
||||
'class' => 'inline-flex items-center hover:underline',
|
||||
'title' => lang('Status.replies', [
|
||||
'title' => lang('Post.replies', [
|
||||
'numberOfReplies' => $reply->replies_count,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
<?= anchor_popup(
|
||||
route_to('status-remote-action', $podcast->handle, $reply->id, 'reblog'),
|
||||
route_to('post-remote-action', $podcast->handle, $reply->id, 'reblog'),
|
||||
icon('repeat', 'text-xl mr-1 text-gray-400') . $reply->reblogs_count,
|
||||
[
|
||||
'class' => 'inline-flex items-center hover:underline',
|
||||
'width' => 420,
|
||||
'height' => 620,
|
||||
'title' => lang('Status.reblogs', [
|
||||
'title' => lang('Post.reblogs', [
|
||||
'numberOfReblogs' => $reply->reblogs_count,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
<?= anchor_popup(
|
||||
route_to('status-remote-action', $podcast->handle, $reply->id, 'favourite'),
|
||||
route_to('post-remote-action', $podcast->handle, $reply->id, 'favourite'),
|
||||
icon('heart', 'text-xl mr-1 text-gray-400') . $reply->favourites_count,
|
||||
[
|
||||
'class' => 'inline-flex items-center hover:underline',
|
||||
'width' => 420,
|
||||
'height' => 620,
|
||||
'title' => lang('Status.favourites', [
|
||||
'title' => lang('Post.favourites', [
|
||||
'numberOfFavourites' => $reply->favourites_count,
|
||||
]),
|
||||
],
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
<footer class="mt-2 text-sm">
|
||||
<form action="<?= route_to(
|
||||
'status-attempt-action',
|
||||
'post-attempt-action',
|
||||
interact_as_actor()->username,
|
||||
$reply->id,
|
||||
) ?>" method="POST" class="flex items-start">
|
||||
<?= csrf_field() ?>
|
||||
<?= anchor(
|
||||
route_to('status', $podcast->handle, $reply->id),
|
||||
route_to('post', $podcast->handle, $reply->id),
|
||||
icon('chat', 'text-xl mr-1 text-gray-400') . $reply->replies_count,
|
||||
[
|
||||
'class' => 'inline-flex items-center mr-6 hover:underline',
|
||||
'title' => lang('Status.replies', [
|
||||
'title' => lang('Post.replies', [
|
||||
'numberOfReplies' => $reply->replies_count,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
<button type="submit" name="action" value="reblog" class="inline-flex items-center mr-6 hover:underline" title="<?= lang(
|
||||
'Status.reblogs',
|
||||
'Post.reblogs',
|
||||
[
|
||||
'numberOfReblogs' => $reply->reblogs_count,
|
||||
],
|
||||
) ?>"><?= icon('repeat', 'text-xl mr-1 text-gray-400') .
|
||||
$reply->reblogs_count ?></button>
|
||||
<button type="submit" name="action" value="favourite" class="inline-flex items-center mr-6 hover:underline" title="<?= lang(
|
||||
'Status.favourites',
|
||||
'Post.favourites',
|
||||
[
|
||||
'numberOfFavourites' => $reply->favourites_count,
|
||||
],
|
||||
|
@ -40,33 +40,33 @@
|
|||
'-more-dropdown-menu' ?>" class="flex flex-col py-2 text-sm bg-white border rounded-lg shadow" aria-labelledby="<?= $reply->id .
|
||||
'-more-dropdown' ?>" data-dropdown="menu" data-dropdown-placement="bottom">
|
||||
<?= anchor(
|
||||
route_to('status', $podcast->handle, $reply->id),
|
||||
lang('Status.expand'),
|
||||
route_to('post', $podcast->handle, $reply->id),
|
||||
lang('Post.expand'),
|
||||
[
|
||||
'class' => 'px-4 py-1 hover:bg-gray-100',
|
||||
],
|
||||
) ?>
|
||||
<form action="<?= route_to(
|
||||
'status-attempt-block-actor',
|
||||
'post-attempt-block-actor',
|
||||
interact_as_actor()->username,
|
||||
$reply->id,
|
||||
) ?>" method="POST">
|
||||
<?= csrf_field() ?>
|
||||
<button class="w-full px-4 py-1 text-left hover:bg-gray-100"><?= lang(
|
||||
'Status.block_actor',
|
||||
'Post.block_actor',
|
||||
[
|
||||
'actorUsername' => $reply->actor->username,
|
||||
],
|
||||
) ?></button>
|
||||
</form>
|
||||
<form action="<?= route_to(
|
||||
'status-attempt-block-domain',
|
||||
'post-attempt-block-domain',
|
||||
interact_as_actor()->username,
|
||||
$reply->id,
|
||||
) ?>" method="POST">
|
||||
<?= csrf_field() ?>
|
||||
<button class="w-full px-4 py-1 text-left hover:bg-gray-100"><?= lang(
|
||||
'Status.block_domain',
|
||||
'Post.block_domain',
|
||||
[
|
||||
'actorDomain' => $reply->actor->domain,
|
||||
],
|
||||
|
@ -75,13 +75,13 @@
|
|||
<?php if ($reply->actor->is_local): ?>
|
||||
<hr class="my-2" />
|
||||
<form action="<?= route_to(
|
||||
'status-attempt-delete',
|
||||
'post-attempt-delete',
|
||||
$reply->actor->username,
|
||||
$reply->id,
|
||||
) ?>" method="POST">
|
||||
<?= csrf_field() ?>
|
||||
<button class="w-full px-4 py-1 font-semibold text-left text-red-600 hover:bg-gray-100"><?= lang(
|
||||
'Status.delete',
|
||||
'Post.delete',
|
||||
) ?></button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
->display_name ?><span class="ml-1 text-sm font-normal text-gray-600">@<?= $reply
|
||||
->actor->username .
|
||||
($reply->actor->is_local ? '' : '@' . $reply->actor->domain) ?></span></a>
|
||||
<?= relative_time($status->published_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?>
|
||||
<?= relative_time($post->published_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?>
|
||||
</header>
|
||||
<p class="mb-2 status-content"><?= $reply->message_html ?></p>
|
||||
<p class="mb-2 post-content"><?= $reply->message_html ?></p>
|
||||
<?php if ($reply->has_preview_card): ?>
|
||||
<?= view('podcast/_partials/preview_card', [
|
||||
'preview_card' => $reply->preview_card,
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
<article class="relative z-10 w-full bg-white shadow rounded-2xl">
|
||||
<header class="flex px-6 py-4">
|
||||
<img src="<?= $status->actor
|
||||
->avatar_image_url ?>" alt="<?= $status->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
|
||||
<div class="flex flex-col min-w-0">
|
||||
<a href="<?= $status->actor
|
||||
->uri ?>" class="flex items-baseline hover:underline" <?= $status
|
||||
->actor->is_local
|
||||
? ''
|
||||
: 'target="_blank" rel="noopener noreferrer"' ?>>
|
||||
<span class="mr-2 font-semibold truncate"><?= $status->actor
|
||||
->display_name ?></span>
|
||||
<span class="text-sm text-gray-500 truncate">@<?= $status->actor
|
||||
->username .
|
||||
($status->actor->is_local
|
||||
? ''
|
||||
: '@' . $status->actor->domain) ?></span>
|
||||
</a>
|
||||
<a href="<?= route_to('status', $podcast->handle, $status->id) ?>"
|
||||
class="text-xs text-gray-500">
|
||||
<?= relative_time($status->published_at) ?>
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
<div class="px-6 mb-4 status-content"><?= $status->message_html ?></div>
|
||||
<?php if ($status->episode_id): ?>
|
||||
<?= view('podcast/_partials/episode_preview_card', [
|
||||
'episode' => $status->episode,
|
||||
]) ?>
|
||||
<?php elseif ($status->has_preview_card): ?>
|
||||
<?= view('podcast/_partials/preview_card', [
|
||||
'preview_card' => $status->preview_card,
|
||||
]) ?>
|
||||
<?php endif; ?>
|
||||
<?= $this->include('podcast/_partials/status_actions') ?>
|
||||
</article>
|
|
@ -1,36 +0,0 @@
|
|||
<footer class="flex justify-around px-6 py-3">
|
||||
<?= anchor(
|
||||
route_to('status', $podcast->handle, $status->id),
|
||||
icon('chat', 'text-2xl mr-1 text-gray-400') . $status->replies_count,
|
||||
[
|
||||
'class' => 'inline-flex items-center hover:underline',
|
||||
'title' => lang('Status.replies', [
|
||||
'numberOfReplies' => $status->replies_count,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
<?= anchor_popup(
|
||||
route_to('status-remote-action', $podcast->handle, $status->id, 'reblog'),
|
||||
icon('repeat', 'text-2xl mr-1 text-gray-400') . $status->reblogs_count,
|
||||
[
|
||||
'class' => 'inline-flex items-center hover:underline',
|
||||
'width' => 420,
|
||||
'height' => 620,
|
||||
'title' => lang('Status.reblogs', [
|
||||
'numberOfReblogs' => $status->reblogs_count,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
<?= anchor_popup(
|
||||
route_to('status-remote-action', $podcast->handle, $status->id, 'favourite'),
|
||||
icon('heart', 'text-2xl mr-1 text-gray-400') . $status->favourites_count,
|
||||
[
|
||||
'class' => 'inline-flex items-center hover:underline',
|
||||
'width' => 420,
|
||||
'height' => 620,
|
||||
'title' => lang('Status.favourites', [
|
||||
'numberOfFavourites' => $status->favourites_count,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
</footer>
|
|
@ -1,36 +0,0 @@
|
|||
<article class="relative z-10 w-full bg-white shadow-md rounded-2xl">
|
||||
<header class="flex px-6 py-4">
|
||||
<img src="<?= $status->actor
|
||||
->avatar_image_url ?>" alt="<?= $status->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
|
||||
<div class="flex flex-col min-w-0">
|
||||
<a href="<?= $status->actor
|
||||
->uri ?>" class="flex items-baseline hover:underline" <?= $status
|
||||
->actor->is_local
|
||||
? ''
|
||||
: 'target="_blank" rel="noopener noreferrer"' ?>>
|
||||
<span class="mr-2 font-semibold truncate"><?= $status->actor
|
||||
->display_name ?></span>
|
||||
<span class="text-sm text-gray-500 truncate">@<?= $status->actor
|
||||
->username .
|
||||
($status->actor->is_local
|
||||
? ''
|
||||
: '@' . $status->actor->domain) ?></span>
|
||||
</a>
|
||||
<a href="<?= route_to('status', $podcast->handle, $status->id) ?>"
|
||||
class="text-xs text-gray-500">
|
||||
<?= relative_time($status->published_at) ?>
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
<div class="px-6 mb-4 status-content"><?= $status->message_html ?></div>
|
||||
<?php if ($status->episode_id): ?>
|
||||
<?= view('podcast/_partials/episode_preview_card', [
|
||||
'episode' => $status->episode,
|
||||
]) ?>
|
||||
<?php elseif ($status->has_preview_card): ?>
|
||||
<?= view('podcast/_partials/preview_card', [
|
||||
'preview_card' => $status->preview_card,
|
||||
]) ?>
|
||||
<?php endif; ?>
|
||||
<?= $this->include('podcast/_partials/status_actions_authenticated') ?>
|
||||
</article>
|
|
@ -40,13 +40,13 @@
|
|||
</nav>
|
||||
|
||||
<section class="max-w-2xl px-6 py-8 mx-auto space-y-8">
|
||||
<?php foreach ($statuses as $status): ?>
|
||||
<?php if ($status->reblog_of_id !== null): ?>
|
||||
<?php foreach ($posts as $post): ?>
|
||||
<?php if ($post->reblog_of_id !== null): ?>
|
||||
<?= view('podcast/_partials/reblog', [
|
||||
'status' => $status->reblog_of_status,
|
||||
'post' => $post->reblog_of_post,
|
||||
]) ?>
|
||||
<?php else: ?>
|
||||
<?= view('podcast/_partials/status', ['status' => $status]) ?>
|
||||
<?= view('podcast/_partials/post', ['post' => $post]) ?>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</section>
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
</nav>
|
||||
|
||||
<section class="max-w-2xl px-6 py-8 mx-auto">
|
||||
<?= form_open(route_to('status-attempt-create', interact_as_actor()->username), [
|
||||
<?= form_open(route_to('post-attempt-create', interact_as_actor()->username), [
|
||||
'class' => 'flex p-4 bg-white shadow rounded-xl',
|
||||
]) ?>
|
||||
<?= csrf_field() ?>
|
||||
|
@ -57,7 +57,7 @@
|
|||
'name' => 'message',
|
||||
'class' => 'form-textarea',
|
||||
'required' => 'required',
|
||||
'placeholder' => lang('Status.form.message_placeholder'),
|
||||
'placeholder' => lang('Post.form.message_placeholder'),
|
||||
],
|
||||
old('message', '', false),
|
||||
['rows' => 2],
|
||||
|
@ -67,7 +67,7 @@
|
|||
'name' => 'episode_url',
|
||||
'class' => 'form-input mb-2',
|
||||
'placeholder' =>
|
||||
lang('Status.form.episode_url_placeholder') .
|
||||
lang('Post.form.episode_url_placeholder') .
|
||||
' (' .
|
||||
lang('Common.optional') .
|
||||
')',
|
||||
|
@ -75,7 +75,7 @@
|
|||
]) ?>
|
||||
|
||||
<?= button(
|
||||
lang('Status.form.submit'),
|
||||
lang('Post.form.submit'),
|
||||
'',
|
||||
['variant' => 'primary', 'size' => 'small'],
|
||||
['type' => 'submit', 'class' => 'self-end'],
|
||||
|
@ -85,13 +85,13 @@
|
|||
<hr class="my-4 border-2 border-pine-100">
|
||||
|
||||
<div class="space-y-8">
|
||||
<?php foreach ($statuses as $status): ?>
|
||||
<?php if ($status->reblog_of_id !== null): ?>
|
||||
<?php foreach ($posts as $post): ?>
|
||||
<?php if ($post->reblog_of_id !== null): ?>
|
||||
<?= view('podcast/_partials/reblog_authenticated', [
|
||||
'status' => $status->reblog_of_status,
|
||||
'post' => $post->reblog_of_post,
|
||||
]) ?>
|
||||
<?php else: ?>
|
||||
<?= view('podcast/_partials/status_authenticated', ['status' => $status]) ?>
|
||||
<?= view('podcast/_partials/post_authenticated', ['post' => $post]) ?>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
|
|
@ -65,46 +65,6 @@
|
|||
<?= format_duration($episode->audio_file_duration) ?>
|
||||
</time>
|
||||
</div>
|
||||
<div class="mb-2 space-x-4 text-sm">
|
||||
<?= anchor(
|
||||
route_to('episode', $podcast->handle, $episode->slug),
|
||||
icon('chat', 'text-xl mr-1 text-gray-400') .
|
||||
$episode->statuses_total,
|
||||
[
|
||||
'class' =>
|
||||
'inline-flex items-center hover:underline',
|
||||
'title' => lang('Episode.total_statuses', [
|
||||
'numberOfTotalStatuses' => $episode->statuses_total,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
<?= anchor(
|
||||
route_to('episode', $podcast->handle, $episode->slug),
|
||||
icon('repeat', 'text-xl mr-1 text-gray-400') .
|
||||
$episode->reblogs_total,
|
||||
[
|
||||
'class' =>
|
||||
'inline-flex items-center hover:underline',
|
||||
'title' => lang('Episode.total_reblogs', [
|
||||
'numberOfTotalReblogs' =>
|
||||
$episode->reblogs_total,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
<?= anchor(
|
||||
route_to('episode', $podcast->handle, $episode->slug),
|
||||
icon('heart', 'text-xl mr-1 text-gray-400') .
|
||||
$episode->favourites_total,
|
||||
[
|
||||
'class' =>
|
||||
'inline-flex items-center hover:underline',
|
||||
'title' => lang('Episode.total_favourites', [
|
||||
'numberOfTotalFavourites' =>
|
||||
$episode->favourites_total,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
</div>
|
||||
<?= location_link($episode->location, 'text-sm mb-4') ?>
|
||||
<?= person_list($episode->persons) ?>
|
||||
<?= play_episode_button($episode->id, $episode->image->thumbnail_url, $episode->title, $podcast->title, $episode->audio_file_web_url, $episode->audio_file_mimetype) ?>
|
||||
|
@ -113,27 +73,26 @@
|
|||
</header>
|
||||
|
||||
<div class="tabset">
|
||||
<?php if ($episode->statuses): ?>
|
||||
<input type="radio" name="tabset" id="comments" aria-controls="comments" checked="checked" />
|
||||
<label for="comments"><?= lang('Episode.comments') . ' (' . $episode->comments_count . ')' ?></label>
|
||||
|
||||
<input type="radio" name="tabset" id="activity" aria-controls="activity" />
|
||||
<label for="activity"><?= lang('Episode.activity') . ' (' . $episode->posts_count . ')' ?></label>
|
||||
|
||||
<input type="radio" name="tabset" id="activity" aria-controls="activity" checked="checked" />
|
||||
<label for="activity"><?= lang('Episode.activity') ?></label>
|
||||
<?php endif; ?>
|
||||
|
||||
<input type="radio" name="tabset" id="description" aria-controls="description" <?= $episode->statuses
|
||||
? ''
|
||||
: 'checked="checked"' ?> />
|
||||
<label for="description" class="<?= $episode->statuses
|
||||
? ''
|
||||
: 'col-span-2' ?>"><?= lang('Episode.description') ?></label>
|
||||
<input type="radio" name="tabset" id="description" aria-controls="description" />
|
||||
<label for="description"><?= lang('Episode.description') ?></label>
|
||||
|
||||
<div class="tab-panels">
|
||||
<?php if ($episode->statuses): ?>
|
||||
<section id="activity" class="space-y-8 tab-panel">
|
||||
<?php foreach ($episode->statuses as $status): ?>
|
||||
<?= view('podcast/_partials/status', ['status' => $status]) ?>
|
||||
<?php endforeach; ?>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
<section id="comments" class="space-y-6 tab-panel">
|
||||
<?php foreach ($episode->comments as $comment): ?>
|
||||
<?= view('podcast/_partials/comment', ['comment' => $comment]) ?>
|
||||
<?php endforeach; ?>
|
||||
</section>
|
||||
<section id="activity" class="space-y-8 tab-panel">
|
||||
<?php foreach ($episode->posts as $post): ?>
|
||||
<?= view('podcast/_partials/post', ['post' => $post]) ?>
|
||||
<?php endforeach; ?>
|
||||
</section>
|
||||
<section id="description" class="prose tab-panel">
|
||||
<?= $episode->getDescriptionHtml('-+Website+-') ?>
|
||||
</section>
|
||||
|
|
|
@ -65,46 +65,6 @@
|
|||
<?= format_duration($episode->audio_file_duration) ?>
|
||||
</time>
|
||||
</div>
|
||||
<div class="mb-2 space-x-4 text-sm">
|
||||
<?= anchor(
|
||||
route_to('episode', $podcast->handle, $episode->slug),
|
||||
icon('chat', 'text-xl mr-1 text-gray-400') .
|
||||
$episode->statuses_total,
|
||||
[
|
||||
'class' =>
|
||||
'inline-flex items-center hover:underline',
|
||||
'title' => lang('Episode.total_statuses', [
|
||||
'numberOfTotalStatuses' => $episode->statuses_total,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
<?= anchor(
|
||||
route_to('episode', $podcast->handle, $episode->slug),
|
||||
icon('repeat', 'text-xl mr-1 text-gray-400') .
|
||||
$episode->reblogs_total,
|
||||
[
|
||||
'class' =>
|
||||
'inline-flex items-center hover:underline',
|
||||
'title' => lang('Episode.total_reblogs', [
|
||||
'numberOfTotalReblogs' =>
|
||||
$episode->reblogs_total,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
<?= anchor(
|
||||
route_to('episode', $podcast->handle, $episode->slug),
|
||||
icon('heart', 'text-xl mr-1 text-gray-400') .
|
||||
$episode->favourites_total,
|
||||
[
|
||||
'class' =>
|
||||
'inline-flex items-center hover:underline',
|
||||
'title' => lang('Episode.total_favourites', [
|
||||
'numberOfTotalFavourites' =>
|
||||
$episode->favourites_total,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
</div>
|
||||
<?= location_link($episode->location, 'text-sm mb-4') ?>
|
||||
<?= person_list($episode->persons) ?>
|
||||
<?= play_episode_button($episode->id, $episode->image->thumbnail_url, $episode->title, $podcast->title, $episode->audio_file_web_url, $episode->audio_file_mimetype) ?>
|
||||
|
@ -113,15 +73,18 @@
|
|||
</header>
|
||||
|
||||
<div class="tabset">
|
||||
<input type="radio" name="tabset" id="activity" aria-controls="activity" checked="checked" />
|
||||
<label for="activity"><?= lang('Episode.activity') ?></label>
|
||||
<input type="radio" name="tabset" id="comments" aria-controls="comments" checked="checked" />
|
||||
<label for="comments"><?= lang('Episode.comments') . ' (' . $episode->comments_count . ')' ?></label>
|
||||
|
||||
<input type="radio" name="tabset" id="activity" aria-controls="activity" />
|
||||
<label for="activity"><?= lang('Episode.activity') . ' ('. $episode->posts_count .')' ?></label>
|
||||
|
||||
<input type="radio" name="tabset" id="description" aria-controls="description" />
|
||||
<label for="description"><?= lang('Episode.description') ?></label>
|
||||
|
||||
<div class="tab-panels">
|
||||
<section id="activity" class="space-y-8 tab-panel">
|
||||
<?= form_open(route_to('status-attempt-create', $podcast->handle), [
|
||||
<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',
|
||||
]) ?>
|
||||
<?= csrf_field() ?>
|
||||
|
@ -129,8 +92,7 @@
|
|||
<?= view('_message_block') ?>
|
||||
|
||||
<img src="<?= interact_as_actor()
|
||||
->avatar_image_url ?>" alt="<?= interact_as_actor()
|
||||
->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
|
||||
->avatar_image_url ?>" alt="<?= interact_as_actor()->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
|
||||
<div class="flex flex-col flex-1 min-w-0">
|
||||
<?= form_textarea(
|
||||
[
|
||||
|
@ -139,7 +101,47 @@
|
|||
'class' => 'form-textarea mb-2',
|
||||
'required' => 'required',
|
||||
'placeholder' => lang(
|
||||
'Status.form.episode_message_placeholder',
|
||||
'Comment.form.episode_message_placeholder',
|
||||
),
|
||||
],
|
||||
old('message', '', false),
|
||||
[
|
||||
'rows' => 2,
|
||||
],
|
||||
) ?>
|
||||
|
||||
<?= button(
|
||||
lang('Comment.form.submit'),
|
||||
'',
|
||||
['variant' => 'primary', 'size' => 'small'],
|
||||
['type' => 'submit', 'class' => 'self-end'],
|
||||
) ?>
|
||||
</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; ?>
|
||||
</section>
|
||||
<section id="activity" class="space-y-8 tab-panel">
|
||||
<?= form_open(route_to('post-attempt-create', $podcast->handle), [
|
||||
'class' => 'flex p-4 bg-white shadow rounded-xl',
|
||||
]) ?>
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<?= view('_message_block') ?>
|
||||
|
||||
<img src="<?= interact_as_actor()
|
||||
->avatar_image_url ?>" alt="<?= interact_as_actor()->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
|
||||
<div class="flex flex-col flex-1 min-w-0">
|
||||
<?= form_textarea(
|
||||
[
|
||||
'id' => 'message',
|
||||
'name' => 'message',
|
||||
'class' => 'form-textarea mb-2',
|
||||
'required' => 'required',
|
||||
'placeholder' => lang(
|
||||
'Post.form.episode_message_placeholder',
|
||||
),
|
||||
],
|
||||
old('message', '', false),
|
||||
|
@ -154,7 +156,7 @@
|
|||
'type' => 'hidden',
|
||||
]) ?>
|
||||
<?= button(
|
||||
lang('Status.form.submit'),
|
||||
lang('Post.form.submit'),
|
||||
'',
|
||||
['variant' => 'primary', 'size' => 'small'],
|
||||
['type' => 'submit', 'class' => 'self-end'],
|
||||
|
@ -162,9 +164,9 @@
|
|||
</div>
|
||||
<?= form_close() ?>
|
||||
<hr class="my-4 border border-pine-100">
|
||||
<?php foreach ($episode->statuses as $status): ?>
|
||||
<?= view('podcast/_partials/status_authenticated', [
|
||||
'status' => $status,
|
||||
<?php foreach ($episode->posts as $post): ?>
|
||||
<?= view('podcast/_partials/post_authenticated', [
|
||||
'post' => $post,
|
||||
]) ?>
|
||||
<?php endforeach; ?>
|
||||
</section>
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<?= $this->extend('podcast/_layout') ?>
|
||||
|
||||
<?= $this->section('meta-tags') ?>
|
||||
<title><?= lang('Post.title', [
|
||||
'actorDisplayName' => $post->actor->display_name,
|
||||
]) ?></title>
|
||||
<meta name="description" content="<?= $post->message ?>"/>
|
||||
<meta property="og:title" content="<?= lang('Post.title', [
|
||||
'actorDisplayName' => $post->actor->display_name,
|
||||
]) ?>"/>
|
||||
<meta property="og:locale" content="<?= service(
|
||||
'request',
|
||||
)->getLocale() ?>" />
|
||||
<meta property="og:site_name" content="<?= $post->actor->display_name ?>" />
|
||||
<meta property="og:url" content="<?= current_url() ?>" />
|
||||
<meta property="og:image" content="<?= $post->actor->avatar_image_url ?>" />
|
||||
<meta property="og:description" content="<?= $post->message ?>" />
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="max-w-2xl px-6 mx-auto">
|
||||
<nav class="py-3">
|
||||
<a href="<?= route_to('podcast-activity', $podcast->handle) ?>"
|
||||
class="inline-flex items-center px-4 py-2 text-sm"><?= icon(
|
||||
'arrow-left',
|
||||
'mr-2 text-lg',
|
||||
) .
|
||||
lang('Post.back_to_actor_posts', [
|
||||
'actor' => $post->actor->display_name,
|
||||
]) ?></a>
|
||||
</nav>
|
||||
<div class="pb-12">
|
||||
<?= $this->include('podcast/_partials/post_with_replies') ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= $this->endSection()
|
||||
?>
|
|
@ -1,20 +1,20 @@
|
|||
<?= $this->extend('podcast/_layout_authenticated') ?>
|
||||
|
||||
<?= $this->section('meta-tags') ?>
|
||||
<title><?= lang('Status.title', [
|
||||
'actorDisplayName' => $status->actor->display_name,
|
||||
<title><?= lang('Post.title', [
|
||||
'actorDisplayName' => $post->actor->display_name,
|
||||
]) ?></title>
|
||||
<meta name="description" content="<?= $status->message ?>"/>
|
||||
<meta property="og:title" content="<?= lang('Status.title', [
|
||||
'actorDisplayName' => $status->actor->display_name,
|
||||
<meta name="description" content="<?= $post->message ?>"/>
|
||||
<meta property="og:title" content="<?= lang('Post.title', [
|
||||
'actorDisplayName' => $post->actor->display_name,
|
||||
]) ?>"/>
|
||||
<meta property="og:locale" content="<?= service(
|
||||
'request',
|
||||
)->getLocale() ?>" />
|
||||
<meta property="og:site_name" content="<?= $status->actor->display_name ?>" />
|
||||
<meta property="og:site_name" content="<?= $post->actor->display_name ?>" />
|
||||
<meta property="og:url" content="<?= current_url() ?>" />
|
||||
<meta property="og:image" content="<?= $status->actor->avatar_image_url ?>" />
|
||||
<meta property="og:description" content="<?= $status->message ?>" />
|
||||
<meta property="og:image" content="<?= $post->actor->avatar_image_url ?>" />
|
||||
<meta property="og:description" content="<?= $post->message ?>" />
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
|
@ -25,13 +25,13 @@
|
|||
'arrow-left',
|
||||
'mr-2 text-lg',
|
||||
) .
|
||||
lang('Status.back_to_actor_statuses', [
|
||||
'actor' => $status->actor->display_name,
|
||||
lang('Post.back_to_actor_posts', [
|
||||
'actor' => $post->actor->display_name,
|
||||
]) ?></a>
|
||||
</nav>
|
||||
<div class="pb-12">
|
||||
<?= $this->include(
|
||||
'podcast/_partials/status_with_replies_authenticated',
|
||||
'podcast/_partials/post_with_replies_authenticated',
|
||||
) ?>
|
||||
</div>
|
||||
</div>
|
|
@ -7,22 +7,22 @@
|
|||
<link rel="shortcut icon" type="image/png" href="/favicon.ico" />
|
||||
|
||||
<title><?= lang('ActivityPub.' . $action . '.title', [
|
||||
'actorDisplayName' => $status->actor->display_name,
|
||||
'actorDisplayName' => $post->actor->display_name,
|
||||
]) ?></title>
|
||||
<meta name="description" content="<?= $status->message ?>"/>
|
||||
<meta name="description" content="<?= $post->message ?>"/>
|
||||
<meta property="og:title" content="<?= lang(
|
||||
'ActivityPub.' . $action . '.title',
|
||||
[
|
||||
'actorDisplayName' => $status->actor->display_name,
|
||||
'actorDisplayName' => $post->actor->display_name,
|
||||
],
|
||||
) ?>"/>
|
||||
<meta property="og:locale" content="<?= service(
|
||||
'request',
|
||||
)->getLocale() ?>" />
|
||||
<meta property="og:site_name" content="<?= $status->actor->display_name ?>" />
|
||||
<meta property="og:site_name" content="<?= $post->actor->display_name ?>" />
|
||||
<meta property="og:url" content="<?= current_url() ?>" />
|
||||
<meta property="og:image" content="<?= $status->actor->avatar_image_url ?>" />
|
||||
<meta property="og:description" content="<?= $status->message ?>" />
|
||||
<meta property="og:image" content="<?= $post->actor->avatar_image_url ?>" />
|
||||
<meta property="og:description" content="<?= $post->message ?>" />
|
||||
|
||||
<?= service('vite')->asset('styles/index.css', 'css') ?>
|
||||
<?= service('vite')->asset('js/podcast.ts', 'js') ?>
|
||||
|
@ -35,10 +35,10 @@
|
|||
) ?></h1>
|
||||
</header>
|
||||
<main class="flex-1 max-w-xl px-4 pb-8 mx-auto -mt-24">
|
||||
<?= $this->include('podcast/_partials/status') ?>
|
||||
<?= $this->include('podcast/_partials/post') ?>
|
||||
|
||||
<?= form_open(
|
||||
route_to('status-attempt-remote-action', $status->id, $action),
|
||||
route_to('post-attempt-remote-action', $post->id, $action),
|
||||
['method' => 'post', 'class' => 'flex flex-col mt-8'],
|
||||
) ?>
|
||||
<?= csrf_field() ?>
|
|
@ -1,38 +0,0 @@
|
|||
<?= $this->extend('podcast/_layout') ?>
|
||||
|
||||
<?= $this->section('meta-tags') ?>
|
||||
<title><?= lang('Status.title', [
|
||||
'actorDisplayName' => $status->actor->display_name,
|
||||
]) ?></title>
|
||||
<meta name="description" content="<?= $status->message ?>"/>
|
||||
<meta property="og:title" content="<?= lang('Status.title', [
|
||||
'actorDisplayName' => $status->actor->display_name,
|
||||
]) ?>"/>
|
||||
<meta property="og:locale" content="<?= service(
|
||||
'request',
|
||||
)->getLocale() ?>" />
|
||||
<meta property="og:site_name" content="<?= $status->actor->display_name ?>" />
|
||||
<meta property="og:url" content="<?= current_url() ?>" />
|
||||
<meta property="og:image" content="<?= $status->actor->avatar_image_url ?>" />
|
||||
<meta property="og:description" content="<?= $status->message ?>" />
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="max-w-2xl px-6 mx-auto">
|
||||
<nav class="py-3">
|
||||
<a href="<?= route_to('podcast-activity', $podcast->handle) ?>"
|
||||
class="inline-flex items-center px-4 py-2 text-sm"><?= icon(
|
||||
'arrow-left',
|
||||
'mr-2 text-lg',
|
||||
) .
|
||||
lang('Status.back_to_actor_statuses', [
|
||||
'actor' => $status->actor->display_name,
|
||||
]) ?></a>
|
||||
</nav>
|
||||
<div class="pb-12">
|
||||
<?= $this->include('podcast/_partials/status_with_replies') ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= $this->endSection()
|
||||
?>
|
Loading…
Reference in New Issue