feat: redesign public podcast and episode pages + remove any information clutter for better ux
- add About podcast page - use different layout for episode pages - improve on user feedback with design - restructure app theme folders - update js packages to latest versions
This commit is contained in:
parent
e3bd9df00e
commit
932140077c
|
@ -114,6 +114,9 @@ $routes->group('@(:podcastHandle)', function ($routes): void {
|
|||
],
|
||||
],
|
||||
]);
|
||||
$routes->get('activity', 'EpisodeController::activity/$1/$2', [
|
||||
'as' => 'episode-activity',
|
||||
]);
|
||||
$routes->options('comments', 'ActivityPubController::preflight');
|
||||
$routes->get('comments', 'EpisodeController::comments/$1/$2', [
|
||||
'as' => 'episode-comments',
|
||||
|
@ -128,7 +131,7 @@ $routes->group('@(:podcastHandle)', function ($routes): void {
|
|||
],
|
||||
]);
|
||||
$routes->get('comments/(:uuid)', 'EpisodeCommentController::view/$1/$2/$3', [
|
||||
'as' => 'comment',
|
||||
'as' => 'episode-comment',
|
||||
'application/activity+json' => [
|
||||
'controller-method' => 'EpisodeController::commentObject/$1/$2',
|
||||
],
|
||||
|
@ -140,10 +143,10 @@ $routes->group('@(:podcastHandle)', function ($routes): void {
|
|||
],
|
||||
]);
|
||||
$routes->get('comments/(:uuid)/replies', 'EpisodeCommentController::replies/$1/$2/$3', [
|
||||
'as' => 'comment-replies',
|
||||
'as' => 'episode-comment-replies',
|
||||
]);
|
||||
$routes->post('comments/(:uuid)/like', 'EpisodeCommentController::attemptLike/$1/$2/$3', [
|
||||
'as' => 'comment-attempt-like',
|
||||
'as' => 'episode-comment-attempt-like',
|
||||
]);
|
||||
$routes->get('oembed.json', 'EpisodeController::oembedJSON/$1/$2', [
|
||||
'as' => 'episode-oembed-json',
|
||||
|
|
|
@ -104,9 +104,8 @@ class EpisodeCommentController extends BaseController
|
|||
// if user is logged in then send to the authenticated activity view
|
||||
if (can_user_interact()) {
|
||||
helper('form');
|
||||
return view('podcast/comment_authenticated', $data);
|
||||
}
|
||||
return view('podcast/comment', $data, [
|
||||
return view('episode/comment', $data, [
|
||||
'cache' => DECADE,
|
||||
'cache_name' => $cacheName,
|
||||
]);
|
||||
|
|
|
@ -87,10 +87,47 @@ class EpisodeController extends BaseController
|
|||
|
||||
if (can_user_interact()) {
|
||||
helper('form');
|
||||
return view('podcast/episode_authenticated', $data);
|
||||
}
|
||||
// The page cache is set to a decade so it is deleted manually upon podcast update
|
||||
return view('podcast/episode', $data, [
|
||||
return view('episode/comments', $data, [
|
||||
'cache' => $secondsToNextUnpublishedEpisode
|
||||
? $secondsToNextUnpublishedEpisode
|
||||
: DECADE,
|
||||
'cache_name' => $cacheName,
|
||||
]);
|
||||
}
|
||||
|
||||
return $cachedView;
|
||||
}
|
||||
|
||||
public function activity(): string
|
||||
{
|
||||
// Prevent analytics hit when authenticated
|
||||
if (! can_user_interact()) {
|
||||
$this->registerPodcastWebpageHit($this->episode->podcast_id);
|
||||
}
|
||||
|
||||
$locale = service('request')
|
||||
->getLocale();
|
||||
$cacheName =
|
||||
"page_podcast#{$this->podcast->id}_episode#{$this->episode->id}_{$locale}" .
|
||||
(can_user_interact() ? '_authenticated' : '');
|
||||
|
||||
if (! ($cachedView = cache($cacheName))) {
|
||||
$data = [
|
||||
'podcast' => $this->podcast,
|
||||
'episode' => $this->episode,
|
||||
];
|
||||
|
||||
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
|
||||
$this->podcast->id,
|
||||
);
|
||||
|
||||
if (can_user_interact()) {
|
||||
helper('form');
|
||||
}
|
||||
// The page cache is set to a decade so it is deleted manually upon podcast update
|
||||
return view('episode/activity', $data, [
|
||||
'cache' => $secondsToNextUnpublishedEpisode
|
||||
? $secondsToNextUnpublishedEpisode
|
||||
: DECADE,
|
||||
|
|
|
@ -87,7 +87,6 @@ class PodcastController extends BaseController
|
|||
// if user is logged in then send to the authenticated activity view
|
||||
if (can_user_interact()) {
|
||||
helper('form');
|
||||
return view('podcast/activity_authenticated', $data);
|
||||
}
|
||||
|
||||
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
|
||||
|
@ -129,10 +128,9 @@ class PodcastController extends BaseController
|
|||
'podcast' => $this->podcast,
|
||||
];
|
||||
|
||||
// if user is logged in then send to the authenticated activity view
|
||||
// // if user is logged in then send to the authenticated activity view
|
||||
if (can_user_interact()) {
|
||||
helper('form');
|
||||
return view('podcast/about_authenticated', $data);
|
||||
}
|
||||
|
||||
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
|
||||
|
@ -256,11 +254,6 @@ class PodcastController extends BaseController
|
|||
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
|
||||
$this->podcast->id,
|
||||
);
|
||||
|
||||
// if user is logged in then send to the authenticated episodes view
|
||||
if (can_user_interact()) {
|
||||
return view('podcast/episodes_authenticated', $data);
|
||||
}
|
||||
return view('podcast/episodes', $data, [
|
||||
'cache' => $secondsToNextUnpublishedEpisode
|
||||
? $secondsToNextUnpublishedEpisode
|
||||
|
|
|
@ -88,9 +88,8 @@ class PostController extends FediversePostController
|
|||
// if user is logged in then send to the authenticated activity view
|
||||
if (can_user_interact()) {
|
||||
helper('form');
|
||||
return view('podcast/post_authenticated', $data);
|
||||
}
|
||||
return view('podcast/post', $data, [
|
||||
return view('post/post', $data, [
|
||||
'cache' => DECADE,
|
||||
'cache_name' => $cacheName,
|
||||
]);
|
||||
|
@ -242,7 +241,7 @@ class PostController extends FediversePostController
|
|||
|
||||
helper('form');
|
||||
|
||||
return view('podcast/post_remote_action', $data, [
|
||||
return view('post/remote_action', $data, [
|
||||
'cache' => DECADE,
|
||||
'cache_name' => $cacheName,
|
||||
]);
|
||||
|
|
|
@ -8,7 +8,6 @@ declare(strict_types=1);
|
|||
* @link https://castopod.org/
|
||||
*/
|
||||
use App\Entities\Location;
|
||||
use App\Entities\Person;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use CodeIgniter\View\Table;
|
||||
|
||||
|
@ -189,11 +188,11 @@ if (! function_exists('episode_numbering')) {
|
|||
$transKey = '';
|
||||
$args = [];
|
||||
if ($episodeNumber !== null) {
|
||||
$args['episodeNumber'] = $episodeNumber;
|
||||
$args['episodeNumber'] = sprintf('%02d', $episodeNumber);
|
||||
}
|
||||
|
||||
if ($seasonNumber !== null) {
|
||||
$args['seasonNumber'] = $seasonNumber;
|
||||
$args['seasonNumber'] = sprintf('%02d', $seasonNumber);
|
||||
}
|
||||
|
||||
if ($episodeNumber !== null && $seasonNumber !== null) {
|
||||
|
@ -250,95 +249,6 @@ if (! function_exists('location_link')) {
|
|||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
if (! function_exists('person_list')) {
|
||||
/**
|
||||
* Returns list of persons images
|
||||
*
|
||||
* @param Person[] $persons
|
||||
*/
|
||||
function person_list(array $persons, string $class = ''): string
|
||||
{
|
||||
if ($persons === []) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$personList = "<div class='flex w-full space-x-2 overflow-y-auto {$class}'>";
|
||||
|
||||
foreach ($persons as $person) {
|
||||
$personList .= anchor(
|
||||
$person->information_url ?? '#',
|
||||
"<img
|
||||
src='{$person->image->thumbnail_url}'
|
||||
alt='{$person->full_name}'
|
||||
class='object-cover w-12 h-12 rounded-full' />",
|
||||
[
|
||||
'class' =>
|
||||
'flex-shrink-0 focus:outline-none focus:ring focus:ring-inset',
|
||||
'target' => '_blank',
|
||||
'rel' => 'noreferrer noopener',
|
||||
'title' =>
|
||||
'<strong>' .
|
||||
$person->full_name .
|
||||
'</strong>' .
|
||||
implode(
|
||||
'',
|
||||
array_map(function ($role) {
|
||||
return '<br />' .
|
||||
lang(
|
||||
'PersonsTaxonomy.persons.' .
|
||||
$role->group .
|
||||
'.roles.' .
|
||||
$role->role .
|
||||
'.label',
|
||||
);
|
||||
}, $person->roles),
|
||||
),
|
||||
'data-toggle' => 'tooltip',
|
||||
'data-placement' => 'bottom',
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return $personList . '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
if (! function_exists('play_episode_button')) {
|
||||
/**
|
||||
* Returns play episode button
|
||||
*/
|
||||
function play_episode_button(
|
||||
string $episodeId,
|
||||
string $episodeThumbnail,
|
||||
string $episodeTitle,
|
||||
string $podcastTitle,
|
||||
string $source,
|
||||
string $mediaType,
|
||||
string $class = ''
|
||||
): string {
|
||||
$playLabel = lang('Common.play_episode_button.play');
|
||||
$playingLabel = lang('Common.play_episode_button.playing');
|
||||
|
||||
return <<<CODE_SAMPLE
|
||||
<play-episode-button
|
||||
class="{$class}"
|
||||
id="{$episodeId}"
|
||||
imageSrc={$episodeThumbnail}
|
||||
title="{$episodeTitle}"
|
||||
podcast="{$podcastTitle}"
|
||||
src="{$source}"
|
||||
mediaType="{$mediaType}"
|
||||
playLabel="{$playLabel}"
|
||||
playingLabel="{$playingLabel}"
|
||||
></play-episode-button>
|
||||
CODE_SAMPLE;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
if (! function_exists('audio_player')) {
|
||||
/**
|
||||
* Returns audio player
|
||||
|
|
|
@ -159,6 +159,34 @@ if (! function_exists('format_duration')) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
if (! function_exists('format_duration_symbol')) {
|
||||
/**
|
||||
* Formats duration in seconds to an hh(h) mm(min) ss(s) string. Doesn't show leading zeros if any.
|
||||
*
|
||||
* ⚠️ This uses php's gmdate function so any duration > 86000 seconds (24 hours) will not be formatted properly.
|
||||
*
|
||||
* @param int $seconds seconds to format
|
||||
*/
|
||||
function format_duration_symbol(int $seconds): string
|
||||
{
|
||||
if ($seconds < 60) {
|
||||
return $seconds . 's';
|
||||
}
|
||||
if ($seconds < 3600) {
|
||||
// < 1 hour: returns MM:SS
|
||||
return ltrim(gmdate('i\m\i\n s\s', $seconds), '0');
|
||||
}
|
||||
if ($seconds < 36000) {
|
||||
// < 10 hours: returns H:MM:SS
|
||||
return ltrim(gmdate('h\h i\min s\s', $seconds), '0');
|
||||
}
|
||||
return gmdate('h\h i\min s\s', $seconds);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
if (! function_exists('podcast_uuid')) {
|
||||
/**
|
||||
* Generate UUIDv5 for podcast. For more information, see
|
||||
|
|
|
@ -14,7 +14,7 @@ return [
|
|||
'form' => [
|
||||
'episode_message_placeholder' => 'Write a comment...',
|
||||
'reply_to_placeholder' => 'Reply to @{actorUsername}',
|
||||
'submit' => 'Send!',
|
||||
'submit' => 'Send',
|
||||
'submit_reply' => 'Reply',
|
||||
],
|
||||
'likes' => '{numberOfLikes, plural,
|
||||
|
|
|
@ -22,7 +22,7 @@ return [
|
|||
'home' => 'Home',
|
||||
'explicit' => 'Explicit',
|
||||
'mediumDate' => '{0,date,medium}',
|
||||
'powered_by' => 'Powered by {castopod}.',
|
||||
'powered_by' => 'Powered by {castopod}',
|
||||
'actions' => 'Actions',
|
||||
'pageInfo' => 'Page {currentPage} out of {pageCount}',
|
||||
'go_back' => 'Go back',
|
||||
|
@ -30,4 +30,8 @@ return [
|
|||
'play' => 'Play',
|
||||
'playing' => 'Playing',
|
||||
],
|
||||
'read_more' => 'Read more',
|
||||
'read_less' => 'Read less',
|
||||
'see_more' => 'See more',
|
||||
'see_less' => 'See less',
|
||||
];
|
||||
|
|
|
@ -15,10 +15,15 @@ return [
|
|||
'number_abbr' => 'Ep. {episodeNumber}',
|
||||
'season_episode' => 'Season {seasonNumber} episode {episodeNumber}',
|
||||
'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}',
|
||||
'persons' => '{personsCount, plural,
|
||||
one {# person}
|
||||
other {# persons}
|
||||
}',
|
||||
'persons_list' => 'Persons',
|
||||
'back_to_episodes' => 'Back to episodes of {podcast}',
|
||||
'comments' => 'Comments',
|
||||
'activity' => 'Activity',
|
||||
'description' => 'Description',
|
||||
'description' => 'Episode description',
|
||||
'number_of_comments' => '{numberOfComments, plural,
|
||||
one {# comment}
|
||||
other {# comments}
|
||||
|
|
|
@ -14,7 +14,7 @@ return [
|
|||
'create' => 'Create podcast',
|
||||
'import' => 'Import podcast',
|
||||
'new_episode' => 'New Episode',
|
||||
'feed' => 'RSS',
|
||||
'feed' => 'RSS Podcast feed',
|
||||
'view' => 'View podcast',
|
||||
'edit' => 'Edit podcast',
|
||||
'delete' => 'Delete podcast',
|
||||
|
@ -48,4 +48,9 @@ return [
|
|||
'funding_links' => 'Funding links for {podcastTitle}',
|
||||
'find_on' => 'Find {podcastTitle} on',
|
||||
'listen_on' => 'Listen on',
|
||||
'persons' => '{personsCount, plural,
|
||||
one {# person}
|
||||
other {# persons}
|
||||
}',
|
||||
'persons_list' => 'Persons',
|
||||
];
|
||||
|
|
|
@ -18,7 +18,7 @@ return [
|
|||
'episode_message_placeholder' => 'Write a message for the episode...',
|
||||
'episode_url_placeholder' => 'Episode URL',
|
||||
'reply_to_placeholder' => 'Reply to @{actorUsername}',
|
||||
'submit' => 'Send!',
|
||||
'submit' => 'Send',
|
||||
'submit_reply' => 'Reply',
|
||||
],
|
||||
'favourites' => '{numberOfFavourites, plural,
|
||||
|
|
|
@ -22,7 +22,7 @@ return [
|
|||
'home' => 'Accueil',
|
||||
'explicit' => 'Explicite',
|
||||
'mediumDate' => '{0,date,medium}',
|
||||
'powered_by' => 'Propulsé par {castopod}.',
|
||||
'powered_by' => 'Propulsé par {castopod}',
|
||||
'actions' => 'Actions',
|
||||
'pageInfo' => 'Page {currentPage} sur {pageCount}',
|
||||
'go_back' => 'Retour en arrière',
|
||||
|
@ -30,4 +30,8 @@ return [
|
|||
'play' => 'Lire',
|
||||
'playing' => 'En cours',
|
||||
],
|
||||
'read_more' => 'Lire plus',
|
||||
'read_less' => 'Lire moins',
|
||||
'see_more' => 'Voir plus',
|
||||
'see_less' => 'Voir moins',
|
||||
];
|
||||
|
|
|
@ -15,10 +15,15 @@ return [
|
|||
'number_abbr' => 'Ep. {episodeNumber}',
|
||||
'season_episode' => 'Saison {seasonNumber} épisode {episodeNumber}',
|
||||
'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}',
|
||||
'persons' => '{personsCount, plural,
|
||||
one {# intervenant·e}
|
||||
other {# intervenant·e·s}
|
||||
}',
|
||||
'persons_list' => 'Liste des intervenant·e·s',
|
||||
'back_to_episodes' => 'Retour aux épisodes de {podcast}',
|
||||
'comments' => 'Commentaires',
|
||||
'activity' => 'Activité',
|
||||
'description' => 'Description',
|
||||
'description' => 'Description de l’épisode',
|
||||
'number_of_comments' => '{numberOfComments, plural,
|
||||
one {# commentaire}
|
||||
other {# commentaires}
|
||||
|
|
|
@ -14,7 +14,7 @@ return [
|
|||
'create' => 'Créer un podcast',
|
||||
'import' => 'Importer un podcast',
|
||||
'new_episode' => 'Créer un épisode',
|
||||
'feed' => 'RSS',
|
||||
'feed' => 'Podcast RSS feed',
|
||||
'view' => 'Voir le podcast',
|
||||
'edit' => 'Modifier le podcast',
|
||||
'delete' => 'Supprimer le podcast',
|
||||
|
@ -48,4 +48,9 @@ return [
|
|||
'funding_links' => 'Liens de financement pour {podcastTitle}',
|
||||
'find_on' => 'Trouvez {podcastTitle} sur',
|
||||
'listen_on' => 'Écoutez sur',
|
||||
'persons' => '{personsCount, plural,
|
||||
one {# intervenant·e}
|
||||
other {# intervenant·e·s}
|
||||
}',
|
||||
'persons_list' => 'Liste des intervenant·e·s',
|
||||
];
|
||||
|
|
|
@ -35,7 +35,12 @@ class CommentObject extends ObjectType
|
|||
$this->inReplyTo = $comment->reply_to_comment->uri;
|
||||
}
|
||||
|
||||
$this->replies = url_to('comment-replies', $comment->actor->username, $comment->episode->slug, $comment->id);
|
||||
$this->replies = url_to(
|
||||
'episode-comment-replies',
|
||||
$comment->actor->username,
|
||||
$comment->episode->slug,
|
||||
$comment->id
|
||||
);
|
||||
|
||||
$this->cc = [$comment->actor->followers_url];
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ class EpisodeCommentModel extends UuidModel
|
|||
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);
|
||||
$comment->uri = url_to('episode-comment', $comment->actor->username, $comment->episode->slug, $comment->id);
|
||||
|
||||
$createActivity = new CreateActivity();
|
||||
$createActivity
|
||||
|
@ -193,7 +193,7 @@ class EpisodeCommentModel extends UuidModel
|
|||
$episode = model('EpisodeModel', false)
|
||||
->find((int) $data['data']['episode_id']);
|
||||
|
||||
$data['data']['uri'] = url_to('comment', $actor->username, $episode->slug, $uuid4->toString());
|
||||
$data['data']['uri'] = url_to('episode-comment', $actor->username, $episode->slug, $uuid4->toString());
|
||||
}
|
||||
|
||||
return $data;
|
||||
|
|
|
@ -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="M3 13h6v-2H3V1.846a.5.5 0 0 1 .741-.438l18.462 10.154a.5.5 0 0 1 0 .876L3.741 22.592A.5.5 0 0 1 3 22.154V13z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 260 B |
|
@ -0,0 +1,3 @@
|
|||
import Dropdown from "./modules/Dropdown";
|
||||
|
||||
Dropdown();
|
|
@ -39,7 +39,7 @@ import "./modules/play-episode-button";
|
|||
|
||||
const player = html`<div
|
||||
id="castopod-audio-player"
|
||||
class="fixed bottom-0 left-0 flex flex-col w-full bg-white border-t sm:flex-row"
|
||||
class="fixed bottom-0 left-0 flex flex-col w-full bg-white border-t sm:flex-row z-50"
|
||||
data-episode="-1"
|
||||
style="display: none;"
|
||||
>
|
||||
|
|
|
@ -188,35 +188,43 @@ export class PlayEpisodeButton extends LitElement {
|
|||
|
||||
static styles = css`
|
||||
button {
|
||||
background-color: #ffffff;
|
||||
background-color: #009486;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem 0.5rem;
|
||||
padding: 0.5rem 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
font-weight: 600;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 9999px;
|
||||
border-color: rgba(207, 247, 243, 1);
|
||||
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border-color: #009486;
|
||||
background-color: #ebf8f8;
|
||||
background-color: #00564a;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
background-color: #ebf8f8;
|
||||
box-shadow: 0 0 0 2px #e7f9e4, 0 0 0 calc(2px + 2px) #009486;
|
||||
}
|
||||
|
||||
button.playing {
|
||||
background-color: #f2faf9;
|
||||
border: 2px solid #009486;
|
||||
}
|
||||
|
||||
button.playing:hover {
|
||||
background-color: #e7f9e4;
|
||||
}
|
||||
|
||||
button.playing svg {
|
||||
color: #009486;
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 1.5rem;
|
||||
margin-right: 0.25rem;
|
||||
color: #009486;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
|
@ -231,33 +239,37 @@ export class PlayEpisodeButton extends LitElement {
|
|||
`;
|
||||
|
||||
render(): TemplateResult<1> {
|
||||
return html`<button @click="${this.isPlaying ? this.pause : this.play}">
|
||||
return html`<button
|
||||
class="${this.isPlaying ? "playing" : ""}"
|
||||
@click="${this.isPlaying ? this.pause : this.play}"
|
||||
title="${this.isPlaying ? this.playingLabel : this.playLabel}"
|
||||
>
|
||||
${this.isPlaying
|
||||
? html`<svg
|
||||
class="animate-spin"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
width="1em"
|
||||
height="1em"
|
||||
>
|
||||
<g>
|
||||
<path fill="none" d="M0 0h24v24H0z" />
|
||||
<path
|
||||
d="M13 9.17A3 3 0 1 0 15 12V2.458c4.057 1.274 7 5.064 7 9.542 0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2c.337 0 .671.017 1 .05v7.12z"
|
||||
/>
|
||||
</g></svg
|
||||
>${this.playingLabel}`
|
||||
: html`<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
width="1em"
|
||||
height="1em"
|
||||
>
|
||||
class="animate-spin"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
width="1em"
|
||||
height="1em"
|
||||
>
|
||||
<g>
|
||||
<path fill="none" d="M0 0h24v24H0z" />
|
||||
<path
|
||||
d="M7.752 5.439l10.508 6.13a.5.5 0 0 1 0 .863l-10.508 6.13A.5.5 0 0 1 7 18.128V5.871a.5.5 0 0 1 .752-.432z"
|
||||
/></svg
|
||||
>${this.playLabel}`}
|
||||
d="M13 9.17A3 3 0 1 0 15 12V2.458c4.057 1.274 7 5.064 7 9.542 0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2c.337 0 .671.017 1 .05v7.12z"
|
||||
/>
|
||||
</g>
|
||||
</svg>`
|
||||
: html`<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
width="1em"
|
||||
height="1em"
|
||||
>
|
||||
<path fill="none" d="M0 0h24v24H0z" />
|
||||
<path
|
||||
d="M7.752 5.439l10.508 6.13a.5.5 0 0 1 0 .863l-10.508 6.13A.5.5 0 0 1 7 18.128V5.871a.5.5 0 0 1 .752-.432z"
|
||||
/>
|
||||
</svg>`}
|
||||
</button>`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import "@github/time-elements";
|
||||
import Dropdown from "./modules/Dropdown";
|
||||
import Time from "./modules/Time";
|
||||
import Toggler from "./modules/Toggler";
|
||||
import Tooltip from "./modules/Tooltip";
|
||||
|
||||
Dropdown();
|
||||
Time();
|
||||
Toggler();
|
||||
Tooltip();
|
||||
|
|
|
@ -12,3 +12,5 @@
|
|||
@import "./radioToggler.css";
|
||||
@import "./formInputTabs.css";
|
||||
@import "./stickyHeader.css";
|
||||
@import "./readMore.css";
|
||||
@import "./seeMore.css";
|
||||
|
|
|
@ -1,34 +1,36 @@
|
|||
/* Admin layout */
|
||||
.holy-grail-grid {
|
||||
@apply grid min-h-screen overflow-y-auto;
|
||||
grid-template: auto 1fr auto / auto 1fr;
|
||||
@layer base {
|
||||
.holy-grail-grid {
|
||||
@apply grid min-h-screen overflow-y-auto;
|
||||
grid-template: 40px 1fr / 300px 1fr;
|
||||
|
||||
& .holy-grail__header {
|
||||
@apply h-10 col-start-1 col-end-4 row-start-1 row-end-2;
|
||||
}
|
||||
& .holy-grail__header {
|
||||
@apply h-10 col-start-1 col-end-4 row-start-1 row-end-2;
|
||||
}
|
||||
|
||||
& .holy-grail__sidebar {
|
||||
@apply col-start-1 col-end-2 row-start-2 row-end-4;
|
||||
& .holy-grail__sidebar {
|
||||
@apply col-start-1 col-end-2 row-start-2 row-end-4;
|
||||
|
||||
width: 300px;
|
||||
max-height: calc(100vh - 2.5rem);
|
||||
}
|
||||
width: 300px;
|
||||
max-height: calc(100vh - 2.5rem);
|
||||
}
|
||||
|
||||
& .holy-grail__main {
|
||||
@apply col-start-1 col-end-3 row-start-2 row-end-4;
|
||||
}
|
||||
|
||||
& .holy-grail__footer {
|
||||
@apply col-start-1 col-end-3 row-start-3 row-end-4;
|
||||
}
|
||||
|
||||
@screen md {
|
||||
& .holy-grail__main {
|
||||
@apply col-start-2;
|
||||
@apply col-start-1 col-end-3 row-start-2 row-end-3;
|
||||
}
|
||||
|
||||
& .holy-grail__footer {
|
||||
@apply col-start-2;
|
||||
@apply col-start-1 col-end-3 row-start-3 row-end-3;
|
||||
}
|
||||
|
||||
@screen md {
|
||||
& .holy-grail__main {
|
||||
@apply col-start-2;
|
||||
}
|
||||
|
||||
& .holy-grail__footer {
|
||||
@apply col-start-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
Read more component (basic unstyled component)
|
||||
-- see https://codepen.io/yassinedoghri/pen/QWpwoxp for more info and possible caveats
|
||||
*/
|
||||
|
||||
@layer components {
|
||||
.read-more {
|
||||
@apply flex flex-col items-start;
|
||||
/* You can update this variable directly in the html with the style attribute: style="--line-clamp: 3" */
|
||||
--line-clamp: 3;
|
||||
}
|
||||
|
||||
.read-more__text {
|
||||
@apply overflow-hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: var(--line-clamp);
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.read-more__checkbox {
|
||||
@apply absolute overflow-hidden whitespace-nowrap;
|
||||
clip: rect(0 0 0 0);
|
||||
clip-path: inset(100%);
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.read-more__checkbox ~ .read-more__label {
|
||||
@apply text-xs font-semibold underline cursor-pointer;
|
||||
}
|
||||
|
||||
/* Don't forget focus and hover styles for accessibility! */
|
||||
.read-more__checkbox:focus ~ .read-more__label {
|
||||
@apply ring;
|
||||
}
|
||||
|
||||
.read-more__checkbox:hover ~ .read-more__label {
|
||||
@apply no-underline;
|
||||
}
|
||||
|
||||
.read-more__checkbox ~ .read-more__label::before {
|
||||
content: attr(data-read-more);
|
||||
}
|
||||
|
||||
.read-more__checkbox:checked ~ .read-more__label::before {
|
||||
content: attr(data-read-less);
|
||||
}
|
||||
|
||||
.read-more__checkbox:checked ~ .read-more__text {
|
||||
--line-clamp: none;
|
||||
-webkit-line-clamp: var(--line-clamp);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
@layer components {
|
||||
.see-more {
|
||||
@apply flex flex-col items-start;
|
||||
}
|
||||
|
||||
.see-more__content {
|
||||
@apply relative overflow-hidden;
|
||||
height: 5rem;
|
||||
}
|
||||
|
||||
.see-more_content-fade {
|
||||
@apply absolute bottom-0 left-0 w-full h-full pointer-events-none;
|
||||
background-image: linear-gradient(to bottom, transparent, #00564a);
|
||||
}
|
||||
|
||||
.see-more__checkbox {
|
||||
@apply absolute overflow-hidden whitespace-nowrap;
|
||||
clip: rect(0 0 0 0);
|
||||
clip-path: inset(100%);
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.see-more__checkbox ~ .see-more__label {
|
||||
@apply text-xs font-semibold underline cursor-pointer;
|
||||
}
|
||||
|
||||
/* Don't forget focus and hover styles for accessibility! */
|
||||
.see-more__checkbox:focus ~ .see-more__label {
|
||||
@apply ring;
|
||||
}
|
||||
|
||||
.see-more__checkbox:hover ~ .see-more__label {
|
||||
@apply no-underline;
|
||||
}
|
||||
|
||||
.see-more__checkbox ~ .see-more__label::before {
|
||||
content: attr(data-see-more);
|
||||
}
|
||||
|
||||
.see-more__checkbox:checked ~ .see-more__label::before {
|
||||
content: attr(data-see-less);
|
||||
}
|
||||
|
||||
.see-more__checkbox:checked ~ .see-more__content {
|
||||
@apply h-auto;
|
||||
}
|
||||
|
||||
.see-more__checkbox:checked ~ .see-more__content .see-more_content-fade {
|
||||
@apply bg-none;
|
||||
}
|
||||
}
|
|
@ -48,7 +48,7 @@ class Button extends Component
|
|||
];
|
||||
|
||||
$basePaddings = [
|
||||
'small' => 'px-2 py-1',
|
||||
'small' => 'px-3 py-1',
|
||||
'base' => 'px-3 py-2',
|
||||
'large' => 'px-4 py-2',
|
||||
];
|
||||
|
|
|
@ -32,6 +32,9 @@ class DropdownMenu extends Component
|
|||
'class' => 'px-4 py-1 hover:bg-gray-100' . (array_key_exists('class', $item) ? ' ' . $item['class'] : ''),
|
||||
]);
|
||||
break;
|
||||
case 'html':
|
||||
$menuItems .= html_entity_decode($item['content']);
|
||||
break;
|
||||
case 'separator':
|
||||
$menuItems .= '<hr class="my-2 border border-gray-100">';
|
||||
break;
|
||||
|
|
|
@ -13,7 +13,7 @@ class MarkdownEditor extends FormComponent
|
|||
$this->attributes['class'] = 'border-none outline-none focus:border-none focus:outline-none focus:ring-0 w-full h-full';
|
||||
$this->attributes['rows'] = 6;
|
||||
|
||||
$textarea = form_textarea($this->attributes, old($this->name, $this->value, false));
|
||||
$textarea = form_textarea($this->attributes, old($this->name, html_entity_decode($this->value), null));
|
||||
$icons = [
|
||||
'heading' => icon('heading'),
|
||||
'bold' => icon('bold'),
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Views\Components;
|
||||
|
||||
use ViewComponents\Component;
|
||||
|
||||
class ReadMore extends Component
|
||||
{
|
||||
public function render(): string
|
||||
{
|
||||
$readMoreLabel = lang('Common.read_more');
|
||||
$readLessLabel = lang('Common.read_less');
|
||||
return <<<HTML
|
||||
<div class="read-more {$this->class}" style="--line-clamp: 3">
|
||||
<input id="read-more-checkbox_{$this->id}" type="checkbox" class="read-more__checkbox" aria-hidden="true">
|
||||
<div class="mb-2 read-more__text">{$this->slot}</div>
|
||||
<label for="read-more-checkbox_{$this->id}" class="read-more__label" data-read-more="{$readMoreLabel}" data-read-less="{$readLessLabel}" aria-hidden="true"></label>
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Views\Components;
|
||||
|
||||
use ViewComponents\Component;
|
||||
|
||||
class SeeMore extends Component
|
||||
{
|
||||
public function render(): string
|
||||
{
|
||||
$seeMoreLabel = lang('Common.see_more');
|
||||
$seeLessLabel = lang('Common.see_less');
|
||||
return <<<HTML
|
||||
<div class="see-more" style="--line-clamp: 3">
|
||||
<input id="see-more-checkbox" type="checkbox" class="see-more__checkbox" aria-hidden="true">
|
||||
<div class="mb-2 see-more__content {$this->class}"><div class="see-more_content-fade"></div>{$this->slot}</div>
|
||||
<label for="see-more-checkbox" class="see-more__label" data-see-more="{$seeMoreLabel}" data-see-less="{$seeLessLabel}" aria-hidden="true"></label>
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ declare(strict_types=1);
|
|||
|
||||
return [
|
||||
'go_to_website' => 'View site',
|
||||
'go_to_admin' => 'Go to admin',
|
||||
'dashboard' => 'Dashboard',
|
||||
'admin' => 'Home',
|
||||
'podcasts' => 'Podcasts',
|
||||
|
|
|
@ -22,7 +22,7 @@ return [
|
|||
'home' => 'Home',
|
||||
'explicit' => 'Explicit',
|
||||
'mediumDate' => '{0,date,medium}',
|
||||
'powered_by' => 'Powered by {castopod}.',
|
||||
'powered_by' => 'Powered by {castopod}',
|
||||
'actions' => 'Actions',
|
||||
'pageInfo' => 'Page {currentPage} out of {pageCount}',
|
||||
'go_back' => 'Go back',
|
||||
|
|
|
@ -15,10 +15,6 @@ return [
|
|||
'number_abbr' => 'Ep. {episodeNumber}',
|
||||
'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',
|
||||
'number_of_comments' => '{numberOfComments, plural,
|
||||
one {# comment}
|
||||
other {# comments}
|
||||
|
|
|
@ -14,7 +14,6 @@ return [
|
|||
'create' => 'Create podcast',
|
||||
'import' => 'Import podcast',
|
||||
'new_episode' => 'New Episode',
|
||||
'feed' => 'RSS',
|
||||
'view' => 'View podcast',
|
||||
'edit' => 'Edit podcast',
|
||||
'delete' => 'Delete podcast',
|
||||
|
|
|
@ -22,7 +22,7 @@ return [
|
|||
'home' => 'Accueil',
|
||||
'explicit' => 'Explicite',
|
||||
'mediumDate' => '{0,date,medium}',
|
||||
'powered_by' => 'Propulsé par {castopod}.',
|
||||
'powered_by' => 'Propulsé par {castopod}',
|
||||
'actions' => 'Actions',
|
||||
'pageInfo' => 'Page {currentPage} sur {pageCount}',
|
||||
'go_back' => 'Retour en arrière',
|
||||
|
|
|
@ -16,9 +16,6 @@ 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',
|
||||
'number_of_comments' => '{numberOfComments, plural,
|
||||
one {# commentaire}
|
||||
other {# commentaires}
|
||||
|
|
|
@ -14,7 +14,6 @@ return [
|
|||
'create' => 'Créer un podcast',
|
||||
'import' => 'Importer un podcast',
|
||||
'new_episode' => 'Créer un épisode',
|
||||
'feed' => 'RSS',
|
||||
'view' => 'Voir le podcast',
|
||||
'edit' => 'Modifier le podcast',
|
||||
'delete' => 'Supprimer le podcast',
|
||||
|
|
|
@ -31,7 +31,6 @@ use RuntimeException;
|
|||
* @property Time $published_at
|
||||
* @property Time $created_at
|
||||
*
|
||||
* @property bool $has_preview_card
|
||||
* @property PreviewCard|null $preview_card
|
||||
*
|
||||
* @property bool $has_replies
|
||||
|
@ -48,8 +47,6 @@ class Post extends UuidEntity
|
|||
|
||||
protected ?PreviewCard $preview_card = null;
|
||||
|
||||
protected bool $has_preview_card = false;
|
||||
|
||||
/**
|
||||
* @var Post[]|null
|
||||
*/
|
||||
|
@ -119,11 +116,6 @@ class Post extends UuidEntity
|
|||
return $this->preview_card;
|
||||
}
|
||||
|
||||
public function getHasPreviewCard(): bool
|
||||
{
|
||||
return $this->getPreviewCard() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Post[]
|
||||
*/
|
||||
|
|
|
@ -15,34 +15,35 @@
|
|||
"@codemirror/commands": "^0.19.5",
|
||||
"@codemirror/lang-xml": "^0.19.2",
|
||||
"@codemirror/state": "^0.19.2",
|
||||
"@codemirror/view": "^0.19.8",
|
||||
"@codemirror/view": "^0.19.9",
|
||||
"@github/clipboard-copy-element": "^1.1.2",
|
||||
"@github/markdown-toolbar-element": "^1.5.1",
|
||||
"@github/markdown-toolbar-element": "^1.5.3",
|
||||
"@github/time-elements": "^3.1.2",
|
||||
"@popperjs/core": "^2.10.2",
|
||||
"@vime/core": "^5.0.33",
|
||||
"@vime/core": "^5.0.34",
|
||||
"choices.js": "^9.0.1",
|
||||
"flatpickr": "^4.6.9",
|
||||
"leaflet": "^1.7.1",
|
||||
"leaflet.markercluster": "^1.5.1",
|
||||
"lit": "^2.0.0",
|
||||
"marked": "^3.0.4",
|
||||
"lit": "^2.0.2",
|
||||
"marked": "^3.0.7",
|
||||
"xml-formatter": "^2.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^13.2.0",
|
||||
"@commitlint/cli": "^13.2.1",
|
||||
"@commitlint/config-conventional": "^13.2.0",
|
||||
"@semantic-release/changelog": "^6.0.0",
|
||||
"@semantic-release/exec": "^6.0.1",
|
||||
"@semantic-release/git": "^10.0.0",
|
||||
"@semantic-release/gitlab": "^7.0.3",
|
||||
"@tailwindcss/aspect-ratio": "^0.3.0",
|
||||
"@tailwindcss/forms": "^0.3.4",
|
||||
"@tailwindcss/line-clamp": "^0.2.1",
|
||||
"@tailwindcss/line-clamp": "^0.2.2",
|
||||
"@tailwindcss/typography": "^0.4.1",
|
||||
"@types/leaflet": "^1.7.5",
|
||||
"@types/marked": "^3.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.32.0",
|
||||
"@typescript-eslint/parser": "^4.32.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"cssnano": "^5.0.8",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
|
@ -51,7 +52,7 @@
|
|||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"husky": "^7.0.2",
|
||||
"is-ci": "^3.0.0",
|
||||
"lint-staged": "^11.1.2",
|
||||
"lint-staged": "^11.2.3",
|
||||
"postcss-import": "^14.0.2",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"postcss-reporter": "^7.0.3",
|
||||
|
@ -64,7 +65,7 @@
|
|||
"tailwindcss": "^2.2.16",
|
||||
"tailwindcss-scroll-snap": "^1.1.0",
|
||||
"typescript": "^4.4.3",
|
||||
"vite": "^2.6.1"
|
||||
"vite": "^2.6.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@amcharts/amcharts4": {
|
||||
|
@ -2098,6 +2099,15 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/aspect-ratio": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/aspect-ratio/-/aspect-ratio-0.3.0.tgz",
|
||||
"integrity": "sha512-DMgWskNJR6FNPLbQ8Xoq/PKV/9DfNKh5dvKB+SM8x7lVl4+pnxlZ3Ns4+yGmurA/ze708HrnCG1tXk85HolJmw==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=2.0.0 || >=3.0.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/forms": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.3.4.tgz",
|
||||
|
@ -5947,20 +5957,6 @@
|
|||
"resolved": "https://registry.npmjs.org/fscreen/-/fscreen-1.2.0.tgz",
|
||||
"integrity": "sha512-hlq4+BU0hlPmwsFjwGGzZ+OZ9N/wq9Ljg/sq3pX+2CD7hrJsX9tJgWWK/wiNTFM212CLHWhicOoqwXyZGGetJg=="
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
|
@ -8205,12 +8201,6 @@
|
|||
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/nanocolors": {
|
||||
"version": "0.2.12",
|
||||
"resolved": "https://registry.npmjs.org/nanocolors/-/nanocolors-0.2.12.tgz",
|
||||
"integrity": "sha512-SFNdALvzW+rVlzqexid6epYdt8H9Zol7xDoQarioEFcFN0JHo4CYNztAxmtfgGTVRCmFlEOqqhBpoFGKqSAMug==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.1.30",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
|
||||
|
@ -18922,6 +18912,13 @@
|
|||
"defer-to-connect": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@tailwindcss/aspect-ratio": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/aspect-ratio/-/aspect-ratio-0.3.0.tgz",
|
||||
"integrity": "sha512-DMgWskNJR6FNPLbQ8Xoq/PKV/9DfNKh5dvKB+SM8x7lVl4+pnxlZ3Ns4+yGmurA/ze708HrnCG1tXk85HolJmw==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@tailwindcss/forms": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.3.4.tgz",
|
||||
|
@ -21927,13 +21924,6 @@
|
|||
"resolved": "https://registry.npmjs.org/fscreen/-/fscreen-1.2.0.tgz",
|
||||
"integrity": "sha512-hlq4+BU0hlPmwsFjwGGzZ+OZ9N/wq9Ljg/sq3pX+2CD7hrJsX9tJgWWK/wiNTFM212CLHWhicOoqwXyZGGetJg=="
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
|
@ -23646,12 +23636,6 @@
|
|||
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
|
||||
"dev": true
|
||||
},
|
||||
"nanocolors": {
|
||||
"version": "0.2.12",
|
||||
"resolved": "https://registry.npmjs.org/nanocolors/-/nanocolors-0.2.12.tgz",
|
||||
"integrity": "sha512-SFNdALvzW+rVlzqexid6epYdt8H9Zol7xDoQarioEFcFN0JHo4CYNztAxmtfgGTVRCmFlEOqqhBpoFGKqSAMug==",
|
||||
"dev": true
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.1.30",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
|
||||
|
|
35
package.json
35
package.json
|
@ -27,62 +27,63 @@
|
|||
"prepare": "is-ci || husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@amcharts/amcharts4-geodata": "^4.1.22",
|
||||
"@amcharts/amcharts4": "^4.10.22",
|
||||
"@amcharts/amcharts4-geodata": "^4.1.22",
|
||||
"@codemirror/basic-setup": "^0.19.0",
|
||||
"@codemirror/commands": "^0.19.5",
|
||||
"@codemirror/lang-xml": "^0.19.2",
|
||||
"@codemirror/state": "^0.19.2",
|
||||
"@codemirror/view": "^0.19.8",
|
||||
"@codemirror/view": "^0.19.9",
|
||||
"@github/clipboard-copy-element": "^1.1.2",
|
||||
"@github/markdown-toolbar-element": "^1.5.1",
|
||||
"@github/markdown-toolbar-element": "^1.5.3",
|
||||
"@github/time-elements": "^3.1.2",
|
||||
"@popperjs/core": "^2.10.2",
|
||||
"@vime/core": "^5.0.33",
|
||||
"@vime/core": "^5.0.34",
|
||||
"choices.js": "^9.0.1",
|
||||
"flatpickr": "^4.6.9",
|
||||
"leaflet.markercluster": "^1.5.1",
|
||||
"leaflet": "^1.7.1",
|
||||
"lit": "^2.0.0",
|
||||
"marked": "^3.0.4",
|
||||
"leaflet.markercluster": "^1.5.1",
|
||||
"lit": "^2.0.2",
|
||||
"marked": "^3.0.7",
|
||||
"xml-formatter": "^2.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^13.2.0",
|
||||
"@commitlint/cli": "^13.2.1",
|
||||
"@commitlint/config-conventional": "^13.2.0",
|
||||
"@semantic-release/changelog": "^6.0.0",
|
||||
"@semantic-release/exec": "^6.0.1",
|
||||
"@semantic-release/git": "^10.0.0",
|
||||
"@semantic-release/gitlab": "^7.0.3",
|
||||
"@tailwindcss/aspect-ratio": "^0.3.0",
|
||||
"@tailwindcss/forms": "^0.3.4",
|
||||
"@tailwindcss/line-clamp": "^0.2.1",
|
||||
"@tailwindcss/line-clamp": "^0.2.2",
|
||||
"@tailwindcss/typography": "^0.4.1",
|
||||
"@types/leaflet": "^1.7.5",
|
||||
"@types/marked": "^3.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.32.0",
|
||||
"@typescript-eslint/parser": "^4.32.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"cssnano": "^5.0.8",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint": "^7.32.0",
|
||||
"husky": "^7.0.2",
|
||||
"is-ci": "^3.0.0",
|
||||
"lint-staged": "^11.1.2",
|
||||
"lint-staged": "^11.2.3",
|
||||
"postcss-import": "^14.0.2",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"postcss-reporter": "^7.0.3",
|
||||
"prettier-plugin-organize-imports": "^2.3.4",
|
||||
"prettier": "2.4.1",
|
||||
"prettier-plugin-organize-imports": "^2.3.4",
|
||||
"semantic-release": "^18.0.0",
|
||||
"stylelint-config-standard": "^22.0.0",
|
||||
"stylelint": "^13.13.1",
|
||||
"stylelint-config-standard": "^22.0.0",
|
||||
"svgo": "^2.7.0",
|
||||
"tailwindcss-scroll-snap": "^1.1.0",
|
||||
"tailwindcss": "^2.2.16",
|
||||
"tailwindcss-scroll-snap": "^1.1.0",
|
||||
"typescript": "^4.4.3",
|
||||
"vite": "^2.6.1"
|
||||
"vite": "^2.6.5"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,css,md,json}": "prettier --write",
|
||||
|
|
|
@ -16,7 +16,6 @@ module.exports = {
|
|||
fontFamily: {
|
||||
sans: ["Inter", ...defaultTheme.fontFamily.sans],
|
||||
display: ["Kumbh Sans", ...defaultTheme.fontFamily.sans],
|
||||
// body: ["Inter", ...defaultTheme.fontFamily.sans],
|
||||
},
|
||||
colors: {
|
||||
pine: {
|
||||
|
@ -49,6 +48,7 @@ module.exports = {
|
|||
112: "28rem",
|
||||
},
|
||||
gridTemplateColumns: {
|
||||
podcastLayout: "1fr minmax(auto, 768px) 1fr",
|
||||
podcasts: "repeat(auto-fill, minmax(14rem, 1fr))",
|
||||
},
|
||||
zIndex: {
|
||||
|
@ -67,6 +67,7 @@ module.exports = {
|
|||
require("@tailwindcss/forms"),
|
||||
require("@tailwindcss/typography"),
|
||||
require("@tailwindcss/line-clamp"),
|
||||
require("@tailwindcss/aspect-ratio"),
|
||||
require("tailwindcss-scroll-snap"),
|
||||
],
|
||||
};
|
||||
|
|
|
@ -19,48 +19,7 @@
|
|||
|
||||
<body class="relative bg-pine-50 holy-grail-grid">
|
||||
<div id="sidebar-backdrop" role="button" tabIndex="0" aria-label="Close" class="fixed z-50 hidden w-full h-full bg-gray-900 bg-opacity-50 md:hidden"></div>
|
||||
<header class="sticky top-0 z-50 flex items-center justify-between h-10 text-white border-b holy-grail__header bg-pine-800 border-pine-900">
|
||||
<div class="inline-flex items-center h-full">
|
||||
<a href="<?= route_to(
|
||||
'admin',
|
||||
) ?>" class="inline-flex items-center h-full px-2 border-r border-pine-900">
|
||||
<?= (isset($podcast) ? icon('arrow-left', 'mr-2') : '') . svg('castopod-logo-base', 'h-6') ?>
|
||||
</a>
|
||||
<a href="<?= route_to(
|
||||
'home',
|
||||
) ?>" class="inline-flex items-center h-full px-6 text-sm font-semibold outline-none hover:underline focus:ring">
|
||||
<?= lang('AdminNavigation.go_to_website') ?>
|
||||
<?= icon('external-link', 'ml-1 opacity-60') ?>
|
||||
</a>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center h-full px-6 mt-auto font-semibold outline-none focus:ring"
|
||||
id="my-account-dropdown"
|
||||
data-dropdown="button"
|
||||
data-dropdown-target="my-account-dropdown-menu"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"><?= icon('account-circle', 'text-2xl opacity-60 mr-2') . user()->username . icon('caret-down', 'ml-auto text-2xl') ?></button>
|
||||
<DropdownMenu id="my-account-dropdown-menu" labelledby="my-account-dropdown" items="<?= esc(json_encode([
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('AdminNavigation.account.my-account'),
|
||||
'uri' => route_to('my-account'),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('AdminNavigation.account.change-password'),
|
||||
'uri' => route_to('change-password'),
|
||||
],
|
||||
[
|
||||
'type' => 'separator',
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('AdminNavigation.account.logout'),
|
||||
'uri' => route_to('logout'),
|
||||
], ])) ?>" />
|
||||
</header>
|
||||
<?= $this->include('_partials/_nav_header') ?>
|
||||
<aside id="admin-sidebar" class="sticky z-50 flex flex-col text-white transition duration-200 ease-in-out transform -translate-x-full border-r top-10 border-pine-900 bg-pine-800 holy-grail__sidebar md:translate-x-0">
|
||||
<?php if (isset($podcast) && isset($episode)): ?>
|
||||
<?= $this->include('episode/_sidebar') ?>
|
||||
|
@ -72,7 +31,7 @@
|
|||
<footer class="px-2 py-2 mx-auto text-xs text-right">
|
||||
<?= lang('Common.powered_by', [
|
||||
'castopod' =>
|
||||
'<a class="underline hover:no-underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod</a> ' .
|
||||
'<a class="inline-flex font-semibold hover:underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a> ' .
|
||||
CP_VERSION,
|
||||
]) ?>
|
||||
</footer>
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
<header class="sticky top-0 z-50 flex items-center justify-between h-10 text-white border-b holy-grail__header bg-pine-800 border-pine-900">
|
||||
<div class="inline-flex items-center h-full">
|
||||
<a href="<?= route_to(
|
||||
'admin',
|
||||
) ?>" class="inline-flex items-center h-full px-2 border-r border-pine-900">
|
||||
<?= (isset($podcast) ? icon('arrow-left', 'mr-2') : '') . svg('castopod-logo-base', 'h-6') ?>
|
||||
</a>
|
||||
<a href="<?= route_to(
|
||||
'home',
|
||||
) ?>" class="inline-flex items-center h-full px-6 text-sm font-semibold outline-none hover:underline focus:ring">
|
||||
<?= lang('AdminNavigation.go_to_website') ?>
|
||||
<?= icon('external-link', 'ml-1 opacity-60') ?>
|
||||
</a>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center h-full px-3 text-sm font-semibold outline-none focus:ring gap-x-2"
|
||||
id="my-account-dropdown"
|
||||
data-dropdown="button"
|
||||
data-dropdown-target="my-account-dropdown-menu"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"><div class="relative mr-1">
|
||||
<?= icon('account-circle', 'text-3xl opacity-60') ?>
|
||||
<?= user()
|
||||
->podcasts === [] ? '' : '<img src="' . interact_as_actor()->avatar_image_url . '" class="absolute bottom-0 w-4 h-4 rounded-full -right-1" />' ?>
|
||||
</div>
|
||||
<?= user()->username ?>
|
||||
<?= icon('caret-down', 'ml-auto text-2xl') ?></button>
|
||||
<?php
|
||||
$interactButtons = '';
|
||||
foreach (user()->podcasts as $userPodcast) {
|
||||
$checkMark = interact_as_actor_id() === $userPodcast->actor_id ? icon('check', 'ml-2 bg-pine-800 text-white rounded-full') : '';
|
||||
|
||||
$interactButtons .= <<<CODE_SAMPLE
|
||||
<button class="inline-flex items-center w-full px-4 py-1 hover:bg-gray-100" id="interact-as-actor-{$userPodcast->id}" name="actor_id" value="{$userPodcast->actor_id}">
|
||||
<span class="inline-flex items-center flex-1 text-sm"><img src="{$userPodcast->image->thumbnail_url}" class="w-6 h-6 mr-2 rounded-full" />{$userPodcast->title}{$checkMark}</span>
|
||||
</button>
|
||||
CODE_SAMPLE;
|
||||
}
|
||||
|
||||
$interactAsText = lang('Admin.choose_interact');
|
||||
$route = route_to('interact-as-actor');
|
||||
$csrfField = csrf_field();
|
||||
|
||||
$menuItems = [
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('AdminNavigation.account.my-account'),
|
||||
'uri' => route_to('my-account'),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('AdminNavigation.account.change-password'),
|
||||
'uri' => route_to('change-password'),
|
||||
],
|
||||
[
|
||||
'type' => 'separator',
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('AdminNavigation.account.logout'),
|
||||
'uri' => route_to('logout'),
|
||||
],
|
||||
];
|
||||
|
||||
if (user()->podcasts !== []) {
|
||||
$menuItems = array_merge([
|
||||
[
|
||||
'type' => 'html',
|
||||
'content' => esc(<<<CODE_SAMPLE
|
||||
<nav class="flex flex-col py-2 text-black whitespace-no-wrap">
|
||||
<span class="px-4 mb-2 text-xs font-semibold tracking-wider text-gray-500 uppercase">{$interactAsText}</span>
|
||||
<form action="{$route}" method="POST" class="flex flex-col">
|
||||
{$csrfField}
|
||||
{$interactButtons}
|
||||
</form>
|
||||
</nav>
|
||||
CODE_SAMPLE),
|
||||
],
|
||||
[
|
||||
'type' => 'separator',
|
||||
],
|
||||
], $menuItems);
|
||||
}
|
||||
?>
|
||||
<DropdownMenu id="my-account-dropdown-menu" labelledby="my-account-dropdown" items="<?= esc(json_encode($menuItems)) ?>" />
|
||||
</header>
|
|
@ -120,7 +120,7 @@
|
|||
as="MarkdownEditor"
|
||||
name="description"
|
||||
label="<?= lang('Episode.form.description') ?>"
|
||||
value="<?= $episode->description_markdown ?>"
|
||||
value="<?= htmlspecialchars($episode->description_markdown) ?>"
|
||||
required="true" />
|
||||
|
||||
<Forms.Field
|
||||
|
@ -128,7 +128,7 @@
|
|||
name="description_footer"
|
||||
label="<?= lang('Episode.form.description_footer') ?>"
|
||||
hint="<?= lang('Episode.form.description_footer_hint') ?>"
|
||||
value="<?= $podcast->episode_description_footer_markdown ?? '' ?>" />
|
||||
value="<?= htmlspecialchars($podcast->episode_description_footer_markdown) ?? '' ?>" />
|
||||
|
||||
</Forms.Section>
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
as="MarkdownEditor"
|
||||
name="content"
|
||||
label="<?= lang('Page.form.content') ?>"
|
||||
value="<?= $page->content_markdown ?>"
|
||||
value="<?= htmlspecialchars($page->content_markdown) ?>"
|
||||
required="true"
|
||||
rows="20" />
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
as="MarkdownEditor"
|
||||
name="description"
|
||||
label="<?= lang('Podcast.form.description') ?>"
|
||||
value="<?= $podcast->title ?>"
|
||||
value="<?= htmlspecialchars($podcast->description) ?>"
|
||||
required="true" />
|
||||
|
||||
<fieldset>
|
||||
|
|
|
@ -63,16 +63,16 @@
|
|||
|
||||
<fieldset class="flex flex-col mb-4">
|
||||
<legend><?= lang('PodcastImport.slug_field') ?></legend>
|
||||
<Forms.Radio id="title" name="slug_field" isChecked="true"><title></span></Forms.Radio>
|
||||
<Forms.Radio id="link" name="slug_field"><link></span></Forms.Radio>
|
||||
<Forms.Radio value="title" name="slug_field" isChecked="true"><title></span></Forms.Radio>
|
||||
<Forms.Radio value="link" name="slug_field"><link></span></Forms.Radio>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="flex flex-col mb-4">
|
||||
<legend><?= lang('PodcastImport.description_field') ?></legend>
|
||||
<Forms.Radio id="description" name="description_field" isChecked="true"><description></Forms.Radio>
|
||||
<Forms.Radio id="summary" name="description_field"><itunes:summary></Forms.Radio>
|
||||
<Forms.Radio id="subtitle_summary" name="description_field"><itunes:subtitle> + <itunes:summary></Forms.Radio>
|
||||
<Forms.Radio id="content" name="description_field"><content:encoded></Forms.Radio>
|
||||
<Forms.Radio value="description" name="description_field" isChecked="true"><description></Forms.Radio>
|
||||
<Forms.Radio value="summary" name="description_field"><itunes:summary></Forms.Radio>
|
||||
<Forms.Radio value="subtitle_summary" name="description_field"><itunes:subtitle> + <itunes:summary></Forms.Radio>
|
||||
<Forms.Radio value="content" name="description_field"><content:encoded></Forms.Radio>
|
||||
</fieldset>
|
||||
|
||||
<Forms.Checkbox name="force_renumber" hint="<?= lang('PodcastImport.force_renumber_hint') ?>"><?= lang('PodcastImport.force_renumber') ?></Forms.Checkbox>
|
||||
|
|
|
@ -1,46 +1,86 @@
|
|||
<div class="sticky top-0 left-0 z-50 flex items-center justify-between w-full h-12 px-4 text-white border-b shadow bg-pine-800 border-pine-900">
|
||||
<?= anchor(
|
||||
route_to('admin'),
|
||||
'castopod' . svg('castopod-logo-base', 'h-5 ml-1'),
|
||||
[
|
||||
'class' =>
|
||||
'text-2xl inline-flex items-baseline font-bold font-display',
|
||||
],
|
||||
) ?>
|
||||
<?php if (user()->podcasts !== []): ?>
|
||||
<button type="button" class="inline-flex items-center px-6 py-2 mt-auto font-semibold outline-none focus:ring" id="interact-as-dropdown" data-dropdown="button" data-dropdown-target="interact-as-dropdown-menu" aria-haspopup="true" aria-expanded="false">
|
||||
<img src="<?= interact_as_actor()
|
||||
->avatar_image_url ?>" class="w-8 h-8 mr-2 rounded-full" />
|
||||
<?= '@' . interact_as_actor()->username ?>
|
||||
<?= icon('caret-down', 'ml-auto') ?>
|
||||
</button>
|
||||
<nav id="interact-as-dropdown-menu" class="absolute z-50 flex flex-col py-2 text-black whitespace-no-wrap bg-white border rounded shadow" aria-labelledby="my-accountDropdown" data-dropdown="menu" data-dropdown-placement="bottom-end">
|
||||
<span class="px-4 text-xs tracking-wider text-gray-700 uppercase"><?= lang(
|
||||
'Admin.choose_interact',
|
||||
) ?></span>
|
||||
<form action="<?= route_to(
|
||||
'interact-as-actor',
|
||||
) ?>" method="POST" class="flex flex-col">
|
||||
<?= csrf_field() ?>
|
||||
<?php foreach (user()->podcasts as $userPodcast): ?>
|
||||
<button class="inline-flex items-center w-full px-4 py-1 hover:bg-gray-100" id="<?= "interact-as-actor-{$userPodcast->id}" ?>" name="actor_id" value="<?= $userPodcast->actor_id ?>">
|
||||
<span class="inline-flex items-center flex-1">
|
||||
<img src="<?= $userPodcast->image
|
||||
->thumbnail_url ?>" class="w-8 h-8 mr-2 rounded-full" /><?= $userPodcast->title ?>
|
||||
<?php if (
|
||||
interact_as_actor()
|
||||
->id ===
|
||||
$userPodcast->actor_id
|
||||
): ?>
|
||||
</span>
|
||||
<?= icon(
|
||||
'check',
|
||||
'ml-4 bg-pine-800 text-white rounded-full',
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
</form>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="sticky top-0 left-0 z-50 flex items-center justify-between w-full h-10 text-white border-b shadow bg-pine-800 border-pine-900">
|
||||
<div class="inline-flex items-center h-full">
|
||||
<a href="<?= route_to('home') ?>" class="inline-flex items-center h-full px-2 border-r border-pine-900">
|
||||
<?= svg('castopod-logo-base', 'h-6') ?>
|
||||
</a>
|
||||
<a href="<?= route_to('admin', ) ?>" class="inline-flex items-center h-full px-6 text-sm font-semibold outline-none hover:underline focus:ring">
|
||||
<?= lang('AdminNavigation.go_to_admin') ?>
|
||||
<?= icon('external-link', 'ml-1 opacity-60') ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="inline-flex items-center h-full">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center h-full px-3 text-sm font-semibold outline-none focus:ring gap-x-2"
|
||||
id="my-account-dropdown"
|
||||
data-dropdown="button"
|
||||
data-dropdown-target="my-account-dropdown-menu"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"><div class="relative mr-1">
|
||||
<?= icon('account-circle', 'text-3xl opacity-60') ?>
|
||||
<?= user()
|
||||
->podcasts === [] ? '' : '<img src="' . interact_as_actor()->avatar_image_url . '" class="absolute bottom-0 w-4 h-4 rounded-full -right-1" />' ?>
|
||||
</div>
|
||||
<?= user()->username ?>
|
||||
<?= icon('caret-down', 'ml-auto text-2xl') ?></button>
|
||||
<?php
|
||||
$interactButtons = '';
|
||||
foreach (user()->podcasts as $userPodcast) {
|
||||
$checkMark = interact_as_actor_id() === $userPodcast->actor_id ? icon('check', 'ml-2 bg-pine-800 text-white rounded-full') : '';
|
||||
|
||||
$interactButtons .= <<<CODE_SAMPLE
|
||||
<button class="inline-flex items-center w-full px-4 py-1 hover:bg-gray-100" id="interact-as-actor-{$userPodcast->id}" name="actor_id" value="{$userPodcast->actor_id}">
|
||||
<span class="inline-flex items-center flex-1 text-sm"><img src="{$userPodcast->image->thumbnail_url}" class="w-6 h-6 mr-2 rounded-full" />{$userPodcast->title}{$checkMark}</span>
|
||||
</button>
|
||||
CODE_SAMPLE;
|
||||
}
|
||||
|
||||
$interactAsText = lang('Admin.choose_interact');
|
||||
$route = route_to('interact-as-actor');
|
||||
$csrfField = csrf_field();
|
||||
|
||||
$menuItems = [
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('AdminNavigation.account.my-account'),
|
||||
'uri' => route_to('my-account'),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('AdminNavigation.account.change-password'),
|
||||
'uri' => route_to('change-password'),
|
||||
],
|
||||
[
|
||||
'type' => 'separator',
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('AdminNavigation.account.logout'),
|
||||
'uri' => route_to('logout'),
|
||||
],
|
||||
];
|
||||
|
||||
if (user()->podcasts !== []) {
|
||||
$menuItems = array_merge([
|
||||
[
|
||||
'type' => 'html',
|
||||
'content' => esc(<<<CODE_SAMPLE
|
||||
<nav class="flex flex-col py-2 text-black whitespace-no-wrap">
|
||||
<span class="px-4 mb-2 text-xs font-semibold tracking-wider text-gray-500 uppercase">{$interactAsText}</span>
|
||||
<form action="{$route}" method="POST" class="flex flex-col">
|
||||
{$csrfField}
|
||||
{$interactButtons}
|
||||
</form>
|
||||
</nav>
|
||||
CODE_SAMPLE),
|
||||
],
|
||||
[
|
||||
'type' => 'separator',
|
||||
],
|
||||
], $menuItems);
|
||||
}
|
||||
?>
|
||||
<DropdownMenu id="my-account-dropdown-menu" labelledby="my-account-dropdown" items="<?= esc(json_encode($menuItems)) ?>" />
|
||||
</div>
|
||||
</div>
|
|
@ -12,11 +12,13 @@
|
|||
|
||||
<?= service('vite')
|
||||
->asset('styles/index.css', 'css') ?>
|
||||
<?= service('vite')
|
||||
->asset('js/app.ts', 'js') ?>
|
||||
<?= service('vite')
|
||||
->asset('js/audio-player.ts', 'js') ?>
|
||||
</head>
|
||||
|
||||
<body class="flex flex-col min-h-screen mx-auto bg-gray-100">
|
||||
<body class="flex flex-col min-h-screen mx-auto bg-pine-50">
|
||||
<?php if (service('authentication')->check()): ?>
|
||||
<?= $this->include('_admin_navbar') ?>
|
||||
<?php endif; ?>
|
||||
|
@ -28,7 +30,7 @@
|
|||
'arrow-left',
|
||||
'mr-2',
|
||||
) . lang('Page.back_to_home') ?></a>
|
||||
<h1 class="text-3xl font-semibold"><?= isset($page)
|
||||
<h1 class="text-3xl font-bold font-display"><?= isset($page)
|
||||
? $page->title
|
||||
: 'Castopod' ?></h1>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
<div id="persons-list" class="fixed top-0 left-0 z-50 flex items-center justify-center hidden w-screen h-screen">
|
||||
<div
|
||||
class="absolute w-full h-full bg-pine-800/75"
|
||||
role="button"
|
||||
data-toggle="persons-list"
|
||||
data-toggle-class="hidden"
|
||||
aria-label="<?= lang('Common.close') ?>"></div>
|
||||
<div class="z-10 w-full max-w-xl bg-white rounded-lg shadow-2xl">
|
||||
<div class="flex justify-between px-4 py-2 border-b">
|
||||
<h3 class="self-center text-lg"><?= $title ?></h3>
|
||||
<button
|
||||
data-toggle="persons-list"
|
||||
data-toggle-class="hidden"
|
||||
aria-label="<?= lang('Common.close') ?>"
|
||||
class="self-start p-1 text-2xl"><?= icon('close') ?></button>
|
||||
</div>
|
||||
<div class="flex flex-col items-start p-4 gap-y-4">
|
||||
<?php foreach ($persons as $person): ?>
|
||||
<div class="flex gap-x-2">
|
||||
<img src="<?= $person->image->thumbnail_url ?>" alt="<?= $person->full_name ?>" class="object-cover w-10 h-10 rounded-full" />
|
||||
<div class="flex flex-col">
|
||||
<h4 class="text-sm font-semibold hover:underline focus:ring">
|
||||
<?php if ($person->information_url): ?>
|
||||
<a href="<?= $person->information_url ?>" target="_blank" rel="noopener noreferrer"><?= $person->full_name ?></a>
|
||||
<?php else: ?>
|
||||
<?= $person->full_name ?>
|
||||
<?php endif; ?>
|
||||
</h4>
|
||||
<p class="text-xs text-gray-500"><?= implode(
|
||||
', ',
|
||||
array_map(function ($role) {
|
||||
return lang(
|
||||
'PersonsTaxonomy.persons.' .
|
||||
$role->group .
|
||||
'.roles.' .
|
||||
$role->role .
|
||||
'.label',
|
||||
);
|
||||
}, $person->roles),
|
||||
) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,143 @@
|
|||
<?= helper('page') ?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?= service('request')
|
||||
->getLocale() ?>">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<link rel="shortcut icon" type="image/png" href="/favicon.ico" />
|
||||
|
||||
<?= $this->renderSection('meta-tags') ?>
|
||||
<?php if ($podcast->payment_pointer): ?>
|
||||
<meta name="monetization" content="<?= $podcast->payment_pointer ?>" />
|
||||
<?php endif; ?>
|
||||
|
||||
<?= service('vite')
|
||||
->asset('styles/index.css', 'css') ?>
|
||||
<?= service('vite')
|
||||
->asset('js/app.ts', 'js') ?>
|
||||
<?= service('vite')
|
||||
->asset('js/podcast.ts', 'js') ?>
|
||||
<?= service('vite')
|
||||
->asset('js/audio-player.ts', 'js') ?>
|
||||
</head>
|
||||
|
||||
<body class="grid items-start mx-auto grid-cols-podcastLayout bg-pine-50">
|
||||
<?php if (can_user_interact()): ?>
|
||||
<div class="col-span-full">
|
||||
<?= $this->include('_admin_navbar') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<nav class="flex items-center justify-between h-10 col-start-2 px-2 text-white bg-pine-800">
|
||||
<a href="<?= route_to('podcast-episodes', $podcast->handle) ?>" class="inline-flex items-center" title="<?= lang('Episode.back_to_episodes', [
|
||||
'podcast' => $podcast->title,
|
||||
]) ?>">
|
||||
<?= icon('arrow-left', 'mr-2 text-lg') ?>
|
||||
<div class="inline-flex items-center gap-x-2">
|
||||
<img class="w-8 h-8 rounded-full" src="<?= $episode->podcast->image->thumbnail_url ?>" alt="<?= $episode->podcast->title ?>" />
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm font-semibold leading-none"><?= $episode->podcast->title ?></span>
|
||||
<span class="text-xs"><?= lang('Podcast.followers', [
|
||||
'numberOfFollowers' => $podcast->actor->followers_count,
|
||||
]) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="inline-flex items-center self-end h-full gap-x-2">
|
||||
<?php if (in_array(true, array_column($podcast->fundingPlatforms, 'is_visible'), true)): ?>
|
||||
<IconButton glyph="heart" variant="accent" data-toggle="funding-links" data-toggle-class="hidden"><?= lang('Podcast.sponsor') . lang('Podcast.sponsor_title') ?></IconButton>
|
||||
<?php endif; ?>
|
||||
<?= anchor_popup(
|
||||
route_to('follow', $podcast->handle),
|
||||
icon(
|
||||
'social/castopod',
|
||||
'mr-2 text-xl text-rose-200 group-hover:text-rose-50',
|
||||
) . lang('Podcast.follow'),
|
||||
[
|
||||
'width' => 420,
|
||||
'height' => 620,
|
||||
'class' =>
|
||||
'group inline-flex items-center px-2 py-1 text-xs tracking-wider font-semibold text-white uppercase rounded-full shadow focus:outline-none focus:ring bg-rose-600',
|
||||
],
|
||||
) ?>
|
||||
</div>
|
||||
</nav>
|
||||
<header class="relative z-50 flex flex-col col-start-2 px-8 pt-8 pb-4 overflow-hidden bg-pine-500 gap-y-4">
|
||||
<div class="absolute top-0 left-0 w-full h-full bg-center bg-no-repeat bg-cover blur-lg mix-blend-luminosity" style="background-image: url('<?= $episode->podcast->image->thumbnail_url ?>');"></div>
|
||||
<div class="absolute top-0 left-0 w-full h-full bg-gradient-to-t from-pine-800 to-transparent"></div>
|
||||
<div class="z-10 flex flex-col items-start gap-y-2 gap-x-4 sm:flex-row">
|
||||
<img src="<?= $episode->image->medium_url ?>" alt="<?= $episode->title ?>" loading="lazy" class="rounded-md h-36" />
|
||||
<div class="flex flex-col items-start text-white">
|
||||
<?= episode_numbering($episode->number, $episode->season_number, 'bg-pine-50 text-sm leading-none font-semibold text-gray-700 border !no-underline border-pine-100', true) ?>
|
||||
<h1 class="inline-flex items-baseline max-w-md mt-2 text-2xl font-bold leading-none sm:text-3xl font-display line-clamp-2"><?= $episode->title ?></h1>
|
||||
<div class="flex items-center mt-4 gap-x-8">
|
||||
<?php if ($episode->persons !== []): ?>
|
||||
<button class="flex items-center text-xs font-semibold gap-x-2 hover:underline" data-toggle="persons-list" data-toggle-class="hidden">
|
||||
<div class="inline-flex flex-row-reverse">
|
||||
<?php $i = 0; ?>
|
||||
<?php foreach ($episode->persons as $person): ?>
|
||||
<img src="<?= $person->image->thumbnail_url ?>" alt="<?= $person->full_name ?>" class="object-cover w-8 h-8 -ml-5 border-2 rounded-full border-pine-100 last:ml-0" />
|
||||
<?php $i++; if ($i === 3) {
|
||||
break;
|
||||
}?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?= lang('Episode.persons', [
|
||||
'personsCount' => count($episode->persons),
|
||||
]) ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if ($episode->location): ?>
|
||||
<?= location_link($episode->location, 'text-xs font-semibold p-2') ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="z-10 inline-flex items-center text-white gap-x-4">
|
||||
<play-episode-button
|
||||
id="<?= $episode->id ?>"
|
||||
imageSrc="<?= $episode->image->thumbnail_url ?>"
|
||||
title="<?= $episode->title ?>"
|
||||
podcast="<?= $episode->podcast->title ?>"
|
||||
src="<?= $episode->audio_file_web_url ?>"
|
||||
mediaType="<?= $episode->audio_file_mimetype ?>"
|
||||
playLabel="<?= lang('Common.play_episode_button.play') ?>"
|
||||
playingLabel="<?= lang('Common.play_episode_button.playing') ?>"></play-episode-button>
|
||||
<div class="text-xs">
|
||||
<?= relative_time($episode->published_at) ?>
|
||||
<span class="mx-1">•</span>
|
||||
<time datetime="PT<?= $episode->audio_file_duration ?>S">
|
||||
<?= format_duration_symbol($episode->audio_file_duration) ?>
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="col-start-2 px-8 py-4 text-white bg-pine-800">
|
||||
<h2 class="text-xs font-bold tracking-wider uppercase whitespace-pre-line font-display"><?= lang('Episode.description') ?></h2>
|
||||
<?php if (substr_count($episode->description_markdown, "\n") > 3 || strlen($episode->description) > 250): ?>
|
||||
<SeeMore class="max-w-xl prose-sm text-white whitespace-pre-line"><?= $episode->getDescriptionHtml('-+Website+-') ?></SeeMore>
|
||||
<?php else: ?>
|
||||
<div class="max-w-xl prose-sm text-white whitespace-pre-line"><?= $episode->getDescriptionHtml('-+Website+-') ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?= $this->include('episode/_partials/navigation') ?>
|
||||
<div class="relative grid items-start grid-cols-3 col-start-2 pt-6 pb-12 gap-x-6">
|
||||
<main class="col-span-full sm:col-span-2">
|
||||
<?= $this->renderSection('content') ?>
|
||||
</main>
|
||||
<?= $this->include('podcast/_partials/sidebar') ?>
|
||||
</div>
|
||||
<?= view('_persons_modal', [
|
||||
'title' => lang('Episode.persons_list', [
|
||||
'episodeTitle' => $episode->title,
|
||||
]),
|
||||
'persons' => $episode->
|
||||
persons,
|
||||
]) ?>
|
||||
<?php if (in_array(true, array_column($podcast->fundingPlatforms, 'is_visible'), true)): ?>
|
||||
<?= $this->include('podcast/_partials/funding_links_modal') ?>
|
||||
<?php endif; ?>
|
||||
</body>
|
|
@ -0,0 +1,29 @@
|
|||
<article class="w-full bg-white shadow sm:rounded-lg">
|
||||
<div class="flex p-4 gap-x-2">
|
||||
<div class="relative">
|
||||
<time class="absolute px-1 text-xs font-semibold text-white rounded bottom-2 right-2 bg-black/50" datetime="PT<?= $episode->audio_file_duration ?>S">
|
||||
<?= format_duration($episode->audio_file_duration) ?>
|
||||
</time>
|
||||
<img loading="lazy" src="<?= $episode->image
|
||||
->thumbnail_url ?>" alt="<?= $episode->title ?>" class="object-cover w-20 h-20 rounded-lg" />
|
||||
</div>
|
||||
<div class="flex items-center flex-1 gap-x-4">
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="inline-flex items-center">
|
||||
<?= episode_numbering($episode->number, $episode->season_number, 'text-xs font-semibold text-gray-700 px-1 border mr-2 !no-underline', true) ?>
|
||||
<?= relative_time($episode->published_at, 'text-xs whitespace-nowrap') ?>
|
||||
</div>
|
||||
<h2 class="flex-1 font-semibold line-clamp-2"><a class="hover:underline" href="<?= $episode->link ?>"><?= $episode->title ?></a></h2>
|
||||
</div>
|
||||
<play-episode-button
|
||||
id="<?= $episode->id ?>"
|
||||
imageSrc="<?= $episode->image->thumbnail_url ?>"
|
||||
title="<?= $episode->title ?>"
|
||||
podcast="<?= $episode->podcast->title ?>"
|
||||
src="<?= $episode->audio_file_web_url ?>"
|
||||
mediaType="<?= $episode->audio_file_mimetype ?>"
|
||||
playLabel="<?= lang('Common.play_episode_button.play') ?>"
|
||||
playingLabel="<?= lang('Common.play_episode_button.playing') ?>"></play-episode-button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
|
@ -1,5 +1,5 @@
|
|||
<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" />
|
||||
<article class="relative z-10 flex w-full px-4 py-2 sm:rounded-2xl">
|
||||
<img src="<?= $comment->actor->avatar_image_url ?>" alt="<?= $comment->display_name ?>" class="w-10 h-10 mr-2 rounded-full" />
|
||||
<div class="flex-1">
|
||||
<header class="w-full mb-2 text-sm">
|
||||
<a href="<?= $comment->actor
|
||||
|
@ -13,14 +13,14 @@
|
|||
($comment->actor->is_local
|
||||
? ''
|
||||
: '@' . $comment->actor->domain) ?></span>
|
||||
<?= relative_time($comment->created_at, 'text-xs text-gray-500 ml-auto') ?>
|
||||
<?= relative_time($comment->created_at, 'text-xs text-gray-500 ml-auto flex-shrink-0') ?>
|
||||
</a>
|
||||
</header>
|
||||
<div class="mb-2 post-content"><?= $comment->message_html ?></div>
|
||||
<?php if ($comment->is_from_post): ?>
|
||||
<?= $this->include('podcast/_partials/comment_actions_from_post') ?>
|
||||
<?= $this->include('episode/_partials/comment_actions_from_post') ?>
|
||||
<?php else: ?>
|
||||
<?= $this->include('podcast/_partials/comment_actions') ?>
|
||||
<?= $this->include('episode/_partials/comment_actions') ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</article>
|
|
@ -0,0 +1,42 @@
|
|||
<footer>
|
||||
<?php if (can_user_interact()): ?>
|
||||
<form action="<?= route_to('comment-attempt-like', interact_as_actor()->username, $comment->episode->slug, $comment->id) ?>" method="POST" class="flex items-center gap-x-4">
|
||||
<button type="submit" name="action" class="inline-flex items-center hover:underline group" title="<?= lang(
|
||||
'Comment.likes',
|
||||
[
|
||||
'numberOfLikes' => $comment->likes_count,
|
||||
],
|
||||
) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . $comment->likes_count ?></button>
|
||||
<Button uri="<?= route_to('episode-comment', $comment->episode->podcast->handle, $comment->episode->slug, $comment->id) ?>" size="small"><?= lang('Comment.reply') ?></Button>
|
||||
</form>
|
||||
<?php if ($comment->replies_count): ?>
|
||||
<?= anchor(
|
||||
route_to('episode-comment', $comment->episode->podcast->handle, $comment->episode->slug, $comment->id),
|
||||
icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', [
|
||||
'numberOfReplies' => $comment->replies_count,
|
||||
]),
|
||||
[
|
||||
'class' => 'inline-flex items-center text-xs hover:underline',
|
||||
]
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<button class="inline-flex items-center opacity-50 cursor-not-allowed hover:underline" title="<?= lang(
|
||||
'Comment.like',
|
||||
[
|
||||
'numberOfLikes' => $comment->likes_count,
|
||||
],
|
||||
) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . $comment->likes_count ?></button>
|
||||
<?php if ($comment->replies_count): ?>
|
||||
<?= anchor(
|
||||
route_to('episode-comment', $comment->episode->podcast->handle, $episode->slug, $comment->id),
|
||||
icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', [
|
||||
'numberOfReplies' => $comment->replies_count,
|
||||
]),
|
||||
[
|
||||
'class' => 'inline-flex items-center text-xs hover:underline',
|
||||
]
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</footer>
|
|
@ -0,0 +1,42 @@
|
|||
<footer>
|
||||
<?php if (can_user_interact()): ?>
|
||||
<form action="<?= route_to('post-attempt-action', interact_as_actor()->username, $comment->id) ?>" method="POST" class="flex items-center gap-x-4">
|
||||
<button type="submit" name="action" value="favourite" class="inline-flex items-center hover:underline group" title="<?= lang(
|
||||
'Comment.likes',
|
||||
[
|
||||
'numberOfLikes' => $comment->likes_count,
|
||||
],
|
||||
) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . $comment->likes_count ?></button>
|
||||
<Button uri="<?= route_to('post', $podcast->handle, $comment->id) ?>" size="small"><?= lang('Comment.reply') ?></Button>
|
||||
</form>
|
||||
<?php if ($comment->replies_count): ?>
|
||||
<?= anchor(
|
||||
route_to('post', $podcast->handle, $comment->id),
|
||||
icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', [
|
||||
'numberOfReplies' => $comment->replies_count,
|
||||
]),
|
||||
[
|
||||
'class' => 'inline-flex items-center text-xs hover:underline',
|
||||
]
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<button class="inline-flex items-center opacity-50 cursor-not-allowed hover:underline" title="<?= lang(
|
||||
'Comment.like',
|
||||
[
|
||||
'numberOfLikes' => $comment->likes_count,
|
||||
],
|
||||
) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . $comment->likes_count ?></button>
|
||||
<?php if ($comment->replies_count): ?>
|
||||
<?= anchor(
|
||||
route_to('post', $podcast->handle, $comment->id),
|
||||
icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', [
|
||||
'numberOfReplies' => $comment->replies_count,
|
||||
]),
|
||||
[
|
||||
'class' => 'inline-flex items-center text-xs hover:underline',
|
||||
]
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</footer>
|
|
@ -1,9 +1,8 @@
|
|||
<article class="relative z-10 flex w-full p-4 bg-white shadow rounded-2xl">
|
||||
<article class="relative z-10 flex w-full p-4 bg-white shadow sm: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 text-sm">
|
||||
<a href="<?= $comment->actor
|
||||
->uri ?>" class="flex items-baseline hover:underline" <?= $comment->actor->is_local
|
||||
<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
|
||||
|
@ -18,10 +17,11 @@
|
|||
</header>
|
||||
<div class="mb-2 post-content"><?= $comment->message_html ?></div>
|
||||
<?php if ($comment->is_from_post): ?>
|
||||
<?= $this->include('podcast/_partials/comment_actions_from_post_authenticated') ?>
|
||||
<?= $this->include('episode/_partials/comment_actions_from_post') ?>
|
||||
<?php else: ?>
|
||||
<footer>
|
||||
<form action="<?= route_to('comment-attempt-like', interact_as_actor()->username, $episode->slug, $comment->id) ?>" method="POST" class="flex items-center gap-x-4">
|
||||
<?php if (can_user_interact()): ?>
|
||||
<form action="<?= route_to('comment-attempt-like', interact_as_actor()->username, $episode->slug, $comment->id) ?>" method="POST" class="flex items-center gap-x-4">
|
||||
<button type="submit" name="action" class="inline-flex items-center hover:underline group" title="<?= lang(
|
||||
'Comment.likes',
|
||||
[
|
||||
|
@ -34,6 +34,19 @@
|
|||
],
|
||||
) ?></button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<button class="inline-flex items-center opacity-50 cursor-not-allowed" title="<?= lang(
|
||||
'Comment.likes',
|
||||
[
|
||||
'numberOfLikes' => $comment->likes_count,
|
||||
],
|
||||
) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . lang(
|
||||
'Comment.likes',
|
||||
[
|
||||
'numberOfLikes' => $comment->likes_count,
|
||||
],
|
||||
) ?></button>
|
||||
<?php endif; ?>
|
||||
</footer>
|
||||
<?php endif; ?>
|
||||
</div>
|
|
@ -14,6 +14,6 @@
|
|||
<?= relative_time($reply->created_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?>
|
||||
</header>
|
||||
<p class="mb-2 post-content"><?= $reply->message_html ?></p>
|
||||
<?= $this->include('podcast/_partials/comment_reply_actions') ?>
|
||||
<?= $this->include('episode/_partials/comment_reply_actions') ?>
|
||||
</div>
|
||||
</article>
|
|
@ -0,0 +1,32 @@
|
|||
<footer>
|
||||
<?php if (can_user_interact()): ?>
|
||||
<form action="<?= route_to('comment-attempt-like', interact_as_actor()->username, $episode->slug, $reply->id) ?>" method="POST" class="flex items-center gap-x-4">
|
||||
<button type="submit" name="action" class="inline-flex items-center hover:underline group" title="<?= lang(
|
||||
'Comment.likes',
|
||||
[
|
||||
'numberOfLikes' => $reply->likes_count,
|
||||
],
|
||||
) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . $reply->likes_count ?></button>
|
||||
<Button uri="<?= route_to('episode-comment', $podcast->handle, $episode->slug, $reply->id) ?>" size="small"><?= lang('Comment.reply') ?></Button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<button type="submit" name="action" class="inline-flex items-center opacity-50 cursor-not-allowed" disabled="disabled" title="<?= lang(
|
||||
'Comment.likes',
|
||||
[
|
||||
'numberOfLikes' => $reply->likes_count,
|
||||
],
|
||||
) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . $reply->likes_count ?></button>
|
||||
<?php if ($reply->replies_count): ?>
|
||||
<?= anchor(
|
||||
route_to('episode-comment', $podcast->handle, $episode->slug, $reply->id),
|
||||
icon('chat', 'text-2xl mr-1 text-gray-400') . $reply->replies_count,
|
||||
[
|
||||
'class' => 'inline-flex items-center hover:underline',
|
||||
'title' => lang('Comment.replies', [
|
||||
'numberOfReplies' => $reply->replies_count,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</footer>
|
|
@ -0,0 +1,29 @@
|
|||
<?= $this->include('episode/_partials/comment_card') ?>
|
||||
<div class="-mt-2 overflow-hidden border-b border-l border-r post-replies rounded-b-xl">
|
||||
|
||||
<?php if (can_user_interact()): ?>
|
||||
<form action="<?= route_to('comment-attempt-reply', $podcast->id, $episode->id, $comment->id) ?>" method="POST" class="flex px-6 pt-8 pb-4 bg-gray-50">
|
||||
<img src="<?= interact_as_actor()
|
||||
->avatar_image_url ?>" alt="<?= interact_as_actor()
|
||||
->display_name ?>" class="w-12 h-12 mr-4 rounded-full ring-gray-50 ring-2" />
|
||||
<div class="flex flex-col flex-1">
|
||||
<Forms.Textarea
|
||||
name="message"
|
||||
required="true"
|
||||
class="w-full mb-4"
|
||||
placeholder="<?= lang('Comment.form.reply_to_placeholder', [
|
||||
'actorUsername' => $comment->actor->username,
|
||||
]) ?>"
|
||||
rows="1" />
|
||||
<Button variant="primary" size="small" type="submit" name="action" value="reply" iconRight="send-plane"><?= lang('Comment.form.submit_reply') ?></Button>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php foreach ($comment->replies as $reply): ?>
|
||||
<?= view('episode/_partials/comment_reply', [
|
||||
'reply' => $reply,
|
||||
]) ?>
|
||||
<?php endforeach; ?>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,22 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
$navigationItems = [
|
||||
[
|
||||
'uri' => route_to('episode', $podcast->handle, $episode->slug),
|
||||
'label' => lang('Episode.comments'),
|
||||
'labelInfo' => $episode->comments_count,
|
||||
],
|
||||
[
|
||||
'uri' => route_to('episode-activity', $podcast->handle, $episode->slug),
|
||||
'label' => lang('Episode.activity'),
|
||||
'labelInfo' => $episode->posts_count,
|
||||
],
|
||||
]
|
||||
?>
|
||||
<nav class="sticky z-40 flex col-start-2 px-4 pt-4 bg-white shadow md:px-8 gap-x-2 md:gap-x-4 -top-4 md:rounded-b-xl">
|
||||
<?php foreach ($navigationItems as $item): ?>
|
||||
<?php $isActive = url_is($item['uri']); ?>
|
||||
<a href="<?= $item['uri'] ?>" class="px-4 py-1 text-sm font-semibold uppercase border-b-4<?= $isActive ? ' border-b-4 text-pine-500 border-pine-500' : ' text-gray-500 hover:text-gray-900 hover:border-gray-200 border-transparent' ?>"><?= $item['label'] ?><span class="px-2 ml-1 font-semibold rounded-full <?= $isActive ? ' bg-pine-100' : ' bg-gray-100' ?>"><?= $item['labelInfo'] ?></span></a>
|
||||
<?php endforeach; ?>
|
||||
<button type="button" class="p-2 ml-auto rotate-180 rounded-full sm:hidden focus:outline-none focus:ring-2 focus:ring-pine-500 focus:ring-offset-2 focus:ring-offset-pine-100" data-toggle="podcast-sidebar" data-toggle-class="absolute sticky top-0 right-0 hidden bg-white top-12"><?= icon('menu') ?></button>
|
||||
</nav>
|
|
@ -0,0 +1,27 @@
|
|||
<div class="flex items-center border-t border-b">
|
||||
<div class="relative">
|
||||
<time class="absolute px-1 text-sm font-semibold text-white bg-black/50 bottom-2 right-2" datetime="PT<?= $episode->audio_file_duration ?>S">
|
||||
<?= format_duration($episode->audio_file_duration) ?>
|
||||
</time>
|
||||
<img
|
||||
src="<?= $episode->image->thumbnail_url ?>"
|
||||
alt="<?= $episode->title ?>" class="w-24 h-24"/>
|
||||
</div>
|
||||
<div class="flex flex-col flex-1 px-4 py-2">
|
||||
<div class="inline-flex">
|
||||
<?= episode_numbering($episode->number, $episode->season_number, 'text-xs font-semibold text-gray-700 px-1 border mr-2 !no-underline', true) ?>
|
||||
<?= relative_time($episode->published_at, 'text-xs whitespace-nowrap text-gray-500') ?>
|
||||
</div>
|
||||
<a href="<?= $episode->link ?>" class="flex items-baseline font-semibold line-clamp-2" title="<?= $episode->title ?>"><?= $episode->title ?></a>
|
||||
</div>
|
||||
<play-episode-button
|
||||
class="mr-4"
|
||||
id="<?= $index . '_' . $episode->id ?>"
|
||||
imageSrc="<?= $episode->image->thumbnail_url ?>"
|
||||
title="<?= $episode->title ?>"
|
||||
podcast="<?= $episode->podcast->title ?>"
|
||||
src="<?= $episode->audio_file_web_url ?>"
|
||||
mediaType="<?= $episode->audio_file_mimetype ?>"
|
||||
playLabel="<?= lang('Common.play_episode_button.play') ?>"
|
||||
playingLabel="<?= lang('Common.play_episode_button.playing') ?>"></play-episode-button>
|
||||
</div>
|
|
@ -0,0 +1,68 @@
|
|||
<?= $this->extend('episode/_layout') ?>
|
||||
|
||||
<?= $this->section('meta-tags') ?>
|
||||
<title><?= $episode->title ?></title>
|
||||
<meta name="description" content="<?= htmlspecialchars($episode->description) ?>" />
|
||||
<link rel="canonical" href="<?= $episode->link ?>" />
|
||||
<meta property="og:title" content="<?= $episode->title ?>" />
|
||||
<meta property="og:description" content="<?= $episode->description ?>" />
|
||||
<meta property="og:locale" content="<?= $podcast->language_code ?>" />
|
||||
<meta property="og:site_name" content="<?= $podcast->title ?>" />
|
||||
<meta property="og:url" content="<?= current_url() ?>" />
|
||||
<meta property="og:image" content="<?= $episode->image->large_url ?>" />
|
||||
<meta property="og:image:width" content="<?= config('Images')
|
||||
->largeSize ?>" />
|
||||
<meta property="og:image:height" content="<?= config('Images')
|
||||
->largeSize ?>" />
|
||||
<meta property="og:description" content="$description" />
|
||||
<meta property="article:published_time" content="<?= $episode->published_at ?>" />
|
||||
<meta property="article:modified_time" content="<?= $episode->updated_at ?>" />
|
||||
<meta property="og:audio" content="<?= $episode->audio_file_opengraph_url ?>" />
|
||||
<meta property="og:audio:type" content="<?= $episode->audio_file_mimetype ?>" />
|
||||
<link rel="alternate" type="application/json+oembed" href="<?= base_url(route_to('episode-oembed-json', $podcast->handle, $episode->slug)) ?>" title="<?= $episode->title ?> oEmbed json" />
|
||||
<link rel="alternate" type="text/xml+oembed" href="<?= base_url(route_to('episode-oembed-xml', $podcast->handle, $episode->slug)) ?>" title="<?= $episode->title ?> oEmbed xml" />
|
||||
<meta name="twitter:title" content="<?= $episode->title ?>" />
|
||||
<meta name="twitter:description" content="<?= $episode->description ?>" />
|
||||
<meta name="twitter:image" content="<?= $episode->image->large_url ?>" />
|
||||
<meta name="twitter:card" content="player" />
|
||||
<meta property="twitter:audio:partner" content="<?= $podcast->publisher ?>" />
|
||||
<meta property="twitter:audio:artist_name" content="<?= $podcast->owner_name ?>" />
|
||||
<meta name="twitter:player" content="<?= $episode->getEmbeddablePlayerUrl('light') ?>" />
|
||||
<meta name="twitter:player:width" content="600" />
|
||||
<meta name="twitter:player:height" content="200" />
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<div class="flex flex-col gap-y-4">
|
||||
<?php if (can_user_interact()): ?>
|
||||
<form action="<?= route_to('post-attempt-create', $podcast->handle) ?>" method="POST" class="flex p-4 bg-white shadow sm:rounded-2xl">
|
||||
<?= 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 gap-y-2">
|
||||
<input name="episode_url" value="<?= $episode->link ?>" type="hidden" />
|
||||
<Forms.Textarea
|
||||
name="message"
|
||||
placeholder="<?= lang('Post.form.episode_message_placeholder') ?>"
|
||||
required="true"
|
||||
rows="2" />
|
||||
<Button variant="primary" size="small" type="submit" class="self-end" iconRight="send-plane"><?= lang('Post.form.submit') ?></Button>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php foreach ($episode->posts as $key => $post): ?>
|
||||
<?= view('post/_partials/card', [
|
||||
'index' => $key,
|
||||
'post' => $post,
|
||||
'podcast' => $podcast,
|
||||
]) ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?= $this->endSection() ?>
|
|
@ -1,4 +1,4 @@
|
|||
<?= $this->extend('podcast/_layout') ?>
|
||||
<?= $this->extend('episode/_layout') ?>
|
||||
|
||||
<?= $this->section('meta-tags') ?>
|
||||
<title><?= lang('Comment.title', [
|
||||
|
@ -30,7 +30,7 @@
|
|||
]) ?></a>
|
||||
</nav>
|
||||
<div class="pb-12">
|
||||
<?= $this->include('podcast/_partials/comment_with_replies') ?>
|
||||
<?= $this->include('episode/_partials/comment_with_replies') ?>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<?= $this->extend('episode/_layout') ?>
|
||||
|
||||
<?= $this->section('meta-tags') ?>
|
||||
<title><?= $episode->title ?></title>
|
||||
<meta name="description" content="<?= htmlspecialchars(
|
||||
$episode->description,
|
||||
) ?>" />
|
||||
<link rel="canonical" href="<?= $episode->link ?>" />
|
||||
<meta property="og:title" content="<?= $episode->title ?>" />
|
||||
<meta property="og:description" content="<?= $episode->description ?>" />
|
||||
<meta property="og:locale" content="<?= $podcast->language_code ?>" />
|
||||
<meta property="og:site_name" content="<?= $podcast->title ?>" />
|
||||
<meta property="og:url" content="<?= current_url() ?>" />
|
||||
<meta property="og:image" content="<?= $episode->image->large_url ?>" />
|
||||
<meta property="og:image:width" content="<?= config('Images')
|
||||
->largeSize ?>" />
|
||||
<meta property="og:image:height" content="<?= config('Images')
|
||||
->largeSize ?>" />
|
||||
<meta property="og:description" content="$description" />
|
||||
<meta property="article:published_time" content="<?= $episode->published_at ?>" />
|
||||
<meta property="article:modified_time" content="<?= $episode->updated_at ?>" />
|
||||
<meta property="og:audio" content="<?= $episode->audio_file_opengraph_url ?>" />
|
||||
<meta property="og:audio:type" content="<?= $episode->audio_file_mimetype ?>" />
|
||||
<link rel="alternate" type="application/json+oembed" href="<?= base_url(route_to('episode-oembed-json', $podcast->handle, $episode->slug)) ?>" title="<?= $episode->title ?> oEmbed json" />
|
||||
<link rel="alternate" type="text/xml+oembed" href="<?= base_url(route_to('episode-oembed-xml', $podcast->handle, $episode->slug)) ?>" title="<?= $episode->title ?> oEmbed xml" />
|
||||
<meta name="twitter:title" content="<?= $episode->title ?>" />
|
||||
<meta name="twitter:description" content="<?= $episode->description ?>" />
|
||||
<meta name="twitter:image" content="<?= $episode->image->large_url ?>" />
|
||||
<meta name="twitter:card" content="player" />
|
||||
<meta property="twitter:audio:partner" content="<?= $podcast->publisher ?>" />
|
||||
<meta property="twitter:audio:artist_name" content="<?= $podcast->owner_name ?>" />
|
||||
<meta name="twitter:player" content="<?= $episode->getEmbeddablePlayerUrl('light') ?>" />
|
||||
<meta name="twitter:player:width" content="600" />
|
||||
<meta name="twitter:player:height" content="200" />
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<div class="flex flex-col gap-y-4">
|
||||
<?php if (can_user_interact()): ?>
|
||||
<form action="<?= route_to('comment-attempt-create', $podcast->id, $episode->id) ?>" method="POST" class="flex p-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<?= view('_message_block') ?>
|
||||
|
||||
<img src="<?= interact_as_actor()
|
||||
->avatar_image_url ?>" alt="<?= interact_as_actor()
|
||||
->display_name ?>" class="w-10 h-10 mr-2 rounded-full" />
|
||||
<div class="flex flex-col flex-1 min-w-0 gap-y-2">
|
||||
<Forms.Textarea
|
||||
name="message"
|
||||
required="true"
|
||||
placeholder="<?= lang('Comment.form.episode_message_placeholder') ?>"
|
||||
rows="2" />
|
||||
<Button class="self-end" variant="primary" size="small" type="submit" iconRight="send-plane"><?= lang('Comment.form.submit') ?></Button>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php foreach ($episode->comments as $comment): ?>
|
||||
<?= view('episode/_partials/comment', [
|
||||
'comment' => $comment,
|
||||
'podcast' => $podcast,
|
||||
]) ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?= $this->endSection()
|
||||
?>
|
|
@ -11,6 +11,8 @@
|
|||
<link rel="shortcut icon" type="image/png" href="/favicon.ico" />
|
||||
<?= service('vite')
|
||||
->asset('styles/index.css', 'css') ?>
|
||||
<?= service('vite')
|
||||
->asset('js/app.ts', 'js') ?>
|
||||
</head>
|
||||
|
||||
<body class="flex flex-col min-h-screen mx-auto bg-pine-50">
|
||||
|
@ -52,7 +54,7 @@
|
|||
<?= render_page_links() ?>
|
||||
<small><?= lang('Common.powered_by', [
|
||||
'castopod' =>
|
||||
'<a class="underline hover:no-underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod</a>',
|
||||
'<a class="inline-flex font-semibold hover:underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a>',
|
||||
]) ?></small>
|
||||
</footer>
|
||||
</body>
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
<link rel="shortcut icon" type="image/png" href="/favicon.ico" />
|
||||
<?= service('vite')
|
||||
->asset('styles/index.css', 'css') ?>
|
||||
<?= service('vite')
|
||||
->asset('js/app.ts', 'js') ?>
|
||||
<?= service('vite')
|
||||
->asset('js/map.ts', 'js') ?>
|
||||
</head>
|
||||
|
@ -37,7 +39,7 @@
|
|||
<?= render_page_links() ?>
|
||||
<small><?= lang('Common.powered_by', [
|
||||
'castopod' =>
|
||||
'<a class="underline hover:no-underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod</a>',
|
||||
'<a class="inline-flex font-semibold hover:underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a>',
|
||||
]) ?></small>
|
||||
</footer>
|
||||
</body>
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
<link rel="shortcut icon" type="image/png" href="/favicon.ico" />
|
||||
<?= service('vite')
|
||||
->asset('styles/index.css', 'css') ?>
|
||||
<?= service('vite')
|
||||
->asset('js/app.ts', 'js') ?>
|
||||
</head>
|
||||
|
||||
<body class="flex flex-col min-h-screen mx-auto">
|
||||
|
@ -36,7 +38,7 @@
|
|||
<?= render_page_links() ?>
|
||||
<small><?= lang('Common.powered_by', [
|
||||
'castopod' =>
|
||||
'<a class="underline hover:no-underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod</a>',
|
||||
'<a class="inline-flex font-semibold hover:underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a>',
|
||||
]) ?></small>
|
||||
</footer>
|
||||
</body>
|
||||
|
|
|
@ -1,123 +0,0 @@
|
|||
<?= helper('page') ?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?= service('request')
|
||||
->getLocale() ?>">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<link rel="shortcut icon" type="image/png" href="/favicon.ico" />
|
||||
|
||||
<?= $this->renderSection('meta-tags') ?>
|
||||
<?php if ($podcast->payment_pointer): ?>
|
||||
<meta name="monetization" content="<?= $podcast->payment_pointer ?>" />
|
||||
<?php endif; ?>
|
||||
|
||||
<?= service('vite')
|
||||
->asset('styles/index.css', 'css') ?>
|
||||
<?= service('vite')
|
||||
->asset('js/podcast.ts', 'js') ?>
|
||||
<?= service('vite')
|
||||
->asset('js/audio-player.ts', 'js') ?>
|
||||
</head>
|
||||
|
||||
<body class="flex w-full min-h-screen pb-20 overflow-x-hidden lg:mx-auto lg:container bg-pine-50 sm:pb-0">
|
||||
<?= $this->include('podcast/_partials/header') ?>
|
||||
|
||||
<main class="flex-shrink-0 w-full min-w-0 sm:w-auto sm:flex-1 sm:flex-shrink">
|
||||
<nav class="sticky top-0 left-0 z-50 flex items-center w-full h-12 px-2 py-1 sm:hidden bg-pine-800">
|
||||
<button
|
||||
data-toggle="main-header"
|
||||
data-toggle-class="sticky -translate-x-full"
|
||||
class="flex-shrink-0 mr-3 overflow-hidden rounded-full focus:ring-2 focus:outline-none focus:ring-pine-50">
|
||||
<img src="<?= $podcast->image
|
||||
->thumbnail_url ?>" alt="<?= $podcast->title ?>" class="h-10"/>
|
||||
</button>
|
||||
<p class="flex flex-col flex-1 min-w-0 mr-2 text-white">
|
||||
<span class="text-sm font-semibold truncate"><?= $podcast->title ?></span>
|
||||
<span class="text-xs">@<?= $podcast->handle ?></span>
|
||||
</p>
|
||||
<?= anchor_popup(
|
||||
route_to('follow', $podcast->handle),
|
||||
icon(
|
||||
'social/castopod',
|
||||
'mr-2 text-xl text-pink-200 group-hover:text-pink-50',
|
||||
) . lang('Podcast.follow'),
|
||||
[
|
||||
'width' => 420,
|
||||
'height' => 620,
|
||||
'class' =>
|
||||
'group inline-flex mr-2 items-center px-3 py-1 text-xs tracking-wider font-semibold text-white uppercase rounded-full shadow focus:outline-none focus:ring bg-rose-600',
|
||||
],
|
||||
) ?>
|
||||
<button
|
||||
data-toggle="main-sidebar"
|
||||
data-toggle-class="translate-x-full"
|
||||
data-toggle-body-class="-ml-64"
|
||||
class="p-2 text-xl rounded-full focus:outline-none focus:ring-2 focus:ring-pine-600 text-pine-200 hover:text-pine-50"><?= icon(
|
||||
'menu',
|
||||
) ?><span class="sr-only"><?= lang('Podcast.toggle_podcast_sidebar') ?></span></button>
|
||||
</nav>
|
||||
<?= $this->renderSection('content') ?>
|
||||
</main>
|
||||
|
||||
<?= $this->include('podcast/_partials/sidebar') ?>
|
||||
|
||||
<button
|
||||
data-toggle="main-sidebar"
|
||||
data-toggle-class="translate-x-full"
|
||||
data-toggle-body-class="-ml-64"
|
||||
class="fixed z-40 hidden p-4 text-xl rounded-full shadow-2xl sm:block lg:hidden bottom-4 left-4 bg-pine-800 focus:outline-none focus:ring-2 focus:ring-pine-600 text-pine-200 hover:text-pine-50"><?= icon(
|
||||
'menu',
|
||||
) ?><span class="sr-only"><?= lang(
|
||||
'Podcast.toggle_podcast_sidebar',
|
||||
) ?></span></button>
|
||||
|
||||
<!-- Funding links modal -->
|
||||
<div id="funding-links" class="fixed top-0 left-0 z-50 flex items-center justify-center hidden w-screen h-screen">
|
||||
<div
|
||||
class="absolute w-full h-full bg-pine-800 bg-opacity-90"
|
||||
role="button"
|
||||
data-toggle="funding-links"
|
||||
data-toggle-class="hidden"
|
||||
aria-label="<?= lang('Common.close') ?>"></div>
|
||||
<div class="z-10 w-full max-w-xl bg-white rounded-lg shadow-2xl">
|
||||
<div class="flex justify-between px-4 py-2 border-b">
|
||||
<h3 class="self-center text-lg"><?= lang(
|
||||
'Podcast.funding_links',
|
||||
[
|
||||
'podcastTitle' => $podcast->title,
|
||||
],
|
||||
) ?></h3>
|
||||
<button
|
||||
data-toggle="funding-links"
|
||||
data-toggle-class="hidden"
|
||||
aria-label="<?= lang('Common.close') ?>"
|
||||
class="self-start p-1 text-2xl"><?= icon('close') ?></button>
|
||||
</div>
|
||||
<div class="flex flex-col items-start p-4 space-y-4">
|
||||
<?php foreach (
|
||||
$podcast->fundingPlatforms
|
||||
as $fundingPlatform
|
||||
): ?>
|
||||
<?php if ($fundingPlatform->is_visible): ?>
|
||||
<a
|
||||
href="<?= $fundingPlatform->link_url ?>"
|
||||
title="<?= $fundingPlatform->link_content ?>"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex items-center font-semibold text-pine-900">
|
||||
<?= icon(
|
||||
$fundingPlatform->type .
|
||||
'/' .
|
||||
$fundingPlatform->slug,
|
||||
'mr-2',
|
||||
) . $fundingPlatform->link_url ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
|
@ -16,118 +16,60 @@
|
|||
|
||||
<?= service('vite')
|
||||
->asset('styles/index.css', 'css') ?>
|
||||
<?= service('vite')
|
||||
->asset('js/app.ts', 'js') ?>
|
||||
<?= service('vite')
|
||||
->asset('js/podcast.ts', 'js') ?>
|
||||
<?= service('vite')
|
||||
->asset('js/audio-player.ts', 'js') ?>
|
||||
</head>
|
||||
|
||||
<body class="grid items-start w-1/2 grid-cols-9 mx-auto bg-pine-50 gap-y-8 gap-x-6">
|
||||
<header class="sticky z-50 flex flex-col bg-white shadow -top-96 rounded-b-xl col-span-full">
|
||||
<div style="background-image: url('<?= $podcast->actor->cover_image_url ?>'); background-size: auto 320px;" class="w-full bg-fixed bg-top bg-no-repeat bg-cover bg-pine-800 h-80"></div>
|
||||
<div class="flex items-center justify-between py-4 ml-8 -mt-28">
|
||||
<div class="flex items-center gap-x-4">
|
||||
<img src="<?= $podcast->image->thumbnail_url ?>" alt="<?= $podcast->title ?>" loading="lazy" class="rounded-full h-36 ring-4 ring-white" />
|
||||
<div class="flex flex-col -mt-4 text-white">
|
||||
<h1 class="inline-flex items-center text-2xl font-bold leading-none font-display"><?= $podcast->title . ($podcast->parental_advisory === 'explicit' ? '<span class="px-1 ml-2 text-xs font-semibold leading-tight tracking-wider text-gray-600 uppercase border-2 border-gray-500">' . lang('Common.explicit') . '</span>' : '') ?></h1>
|
||||
<a href="#" class="hover:underline"><?= lang('Podcast.followers', [
|
||||
'numberOfFollowers' => $podcast->actor->followers_count,
|
||||
]) ?></a>
|
||||
</div>
|
||||
</div>
|
||||
<body class="grid items-start mx-auto grid-cols-podcastLayout bg-pine-50">
|
||||
<?php if (can_user_interact()): ?>
|
||||
<div class="col-span-full">
|
||||
<?= $this->include('_admin_navbar') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="inline-flex items-center mr-4 -mt-4 gap-x-2">
|
||||
<?php if (in_array(true, array_column($podcast->fundingPlatforms, 'is_visible'), true)): ?>
|
||||
<IconButton glyph="heart" data-toggle="funding-links" data-toggle-class="hidden"><?= lang('Podcast.sponsor') . lang('Podcast.sponsor_title') ?></IconButton>
|
||||
<?php endif; ?>
|
||||
<?= anchor_popup(
|
||||
route_to('follow', $podcast->handle),
|
||||
icon(
|
||||
'social/castopod',
|
||||
'mr-2 text-xl text-pink-200 group-hover:text-pink-50',
|
||||
) . lang('Podcast.follow'),
|
||||
[
|
||||
'width' => 420,
|
||||
'height' => 620,
|
||||
'class' =>
|
||||
'group inline-flex items-center px-4 py-2 text-xs tracking-wider font-semibold text-white uppercase rounded-full shadow focus:outline-none focus:ring bg-rose-600',
|
||||
],
|
||||
) ?>
|
||||
<header class="z-50 flex flex-col-reverse justify-between w-full col-start-2 bg-top bg-no-repeat bg-cover sm:flex-row sm:items-end bg-pine-800" style="background-image: url('<?= $podcast->actor->cover_image_url ?>'); aspect-ratio: 15 / 5;">
|
||||
<div class="flex items-center pl-4 -mb-6 md:pl-8 md:-mb-8 gap-x-4">
|
||||
<img src="<?= $podcast->image->thumbnail_url ?>" alt="<?= $podcast->title ?>" loading="lazy" class="h-24 rounded-full md:h-28 ring-4 ring-white" />
|
||||
<div class="relative flex flex-col text-white -top-2">
|
||||
<h1 class="text-lg font-bold leading-none line-clamp-2 md:leading-none md:text-2xl font-display"><?= $podcast->title ?><span class="ml-1 font-sans text-base font-normal">@<?= $podcast->handle ?></span></h1>
|
||||
<span class="text-xs"><?= lang('Podcast.followers', [
|
||||
'numberOfFollowers' => $podcast->actor->followers_count,
|
||||
]) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?= $this->include('podcast/_navigation') ?>
|
||||
</header>
|
||||
|
||||
<main class="col-span-6">
|
||||
<?= $this->renderSection('content') ?>
|
||||
</main>
|
||||
|
||||
<aside class="sticky col-span-3 top-12">
|
||||
<?php if (
|
||||
in_array(true, array_column($podcast->socialPlatforms, 'is_visible'), true)
|
||||
): ?>
|
||||
<h2 class="font-semibold"> <?= lang('Podcast.find_on', [
|
||||
'podcastTitle' => $podcast->title,
|
||||
]) ?></h2>
|
||||
<div class="grid items-center justify-center grid-cols-6 gap-3 mt-2">
|
||||
<?php foreach ($podcast->socialPlatforms as $socialPlatform): ?>
|
||||
<?php if ($socialPlatform->is_visible): ?>
|
||||
<?= anchor(
|
||||
$socialPlatform->link_url,
|
||||
icon("{$socialPlatform->type}/{$socialPlatform->slug}"),
|
||||
[
|
||||
'class' => 'text-2xl text-gray-500 hover:text-gray-700 w-8 h-8 items-center inline-flex justify-center',
|
||||
'target' => '_blank',
|
||||
'rel' => 'noopener noreferrer',
|
||||
'data-toggle' => 'tooltip',
|
||||
'data-placement' => 'bottom',
|
||||
'title' => $socialPlatform->label,
|
||||
],
|
||||
) ?>
|
||||
<div class="inline-flex items-center self-end mt-2 mb-2 mr-2 gap-x-2">
|
||||
<?php if (in_array(true, array_column($podcast->fundingPlatforms, 'is_visible'), true)): ?>
|
||||
<IconButton glyph="heart" variant="accent" data-toggle="funding-links" data-toggle-class="hidden"><?= lang('Podcast.sponsor') . lang('Podcast.sponsor_title') ?></IconButton>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
<?= anchor_popup(
|
||||
route_to('follow', $podcast->handle),
|
||||
icon(
|
||||
'social/castopod',
|
||||
'mr-2 text-xl text-rose-200 group-hover:text-rose-50',
|
||||
) . lang('Podcast.follow'),
|
||||
[
|
||||
'width' => 420,
|
||||
'height' => 620,
|
||||
'class' =>
|
||||
'group inline-flex items-center px-2 py-1 text-xs tracking-wider font-semibold text-white uppercase rounded-full shadow focus:outline-none focus:ring bg-rose-600',
|
||||
],
|
||||
) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</header>
|
||||
<?= $this->include('podcast/_partials/navigation') ?>
|
||||
<div class="grid items-start grid-cols-3 col-start-2 pb-12 mt-6 gap-x-6">
|
||||
<main class="col-span-full sm:col-span-2">
|
||||
<?= $this->renderSection('content') ?>
|
||||
</main>
|
||||
<?= $this->include('podcast/_partials/sidebar') ?>
|
||||
</div>
|
||||
|
||||
<?php if (in_array(true, array_column($podcast->fundingPlatforms, 'is_visible'), true)): ?>
|
||||
<?= $this->include('podcast/_partials/funding_links_modal') ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<h2 class="mt-6 font-semibold"><?= lang('Podcast.listen_on') ?></h2>
|
||||
<div class="grid items-center justify-center grid-cols-6 gap-3 mt-2">
|
||||
<?= anchor(route_to('podcast_feed', $podcast->handle), icon('rss'), [
|
||||
'class' =>
|
||||
'bg-orange-500 text-xl text-white hover:bg-orange-700 w-8 h-8 inline-flex items-center justify-center rounded-lg',
|
||||
'target' => '_blank',
|
||||
'rel' => 'noopener noreferrer',
|
||||
'data-toggle' => 'tooltip',
|
||||
'data-placement' => 'bottom',
|
||||
'title' => lang('Podcast.feed'),
|
||||
]) ?>
|
||||
<?php foreach ($podcast->podcastingPlatforms as $podcastingPlatform): ?>
|
||||
<?php if ($podcastingPlatform->is_visible): ?>
|
||||
<?= anchor(
|
||||
$podcastingPlatform->link_url,
|
||||
icon(
|
||||
"{$podcastingPlatform->type}/{$podcastingPlatform->slug}",
|
||||
),
|
||||
[
|
||||
'class' => 'text-2xl text-gray-500 hover:text-gray-700 w-8 h-8 items-center inline-flex justify-center',
|
||||
'target' => '_blank',
|
||||
'rel' => 'noopener noreferrer',
|
||||
'data-toggle' => 'tooltip',
|
||||
'data-placement' => 'bottom',
|
||||
'title' => $podcastingPlatform->label,
|
||||
],
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<footer class="flex flex-col items-center py-2 mt-8 text-xs text-center text-gray-600 border-t">
|
||||
<?= render_page_links('inline-flex mb-2 flex-wrap gap-y-1') ?>
|
||||
<div class="flex flex-col">
|
||||
<p><?= $podcast->copyright ?></p>
|
||||
<p><?= lang('Common.powered_by', [
|
||||
'castopod' =>
|
||||
'<a class="inline-flex font-semibold hover:underline" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a>',
|
||||
]) ?></p>
|
||||
</div>
|
||||
</footer>
|
||||
</aside>
|
||||
</body>
|
||||
|
|
|
@ -1,115 +0,0 @@
|
|||
<?= helper('page') ?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?= service('request')
|
||||
->getLocale() ?>">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="shortcut icon" type="image/png" href="/favicon.ico" />
|
||||
|
||||
<?= $this->renderSection('meta-tags') ?>
|
||||
<?php if ($podcast->payment_pointer): ?>
|
||||
<meta name="monetization" content="<?= $podcast->payment_pointer ?>" />
|
||||
<?php endif; ?>
|
||||
|
||||
<?= service('vite')
|
||||
->asset('styles/index.css', 'css') ?>
|
||||
<?= service('vite')
|
||||
->asset('js/podcast.ts', 'js') ?>
|
||||
<?= service('vite')
|
||||
->asset('js/audio-player.ts', 'js') ?>
|
||||
</head>
|
||||
|
||||
<body class="w-full min-h-screen pb-20 overflow-x-hidden bg-pine-50 lg:mx-auto sm:pb-0">
|
||||
<?= $this->include('_admin_navbar') ?>
|
||||
<div class="flex">
|
||||
|
||||
<?= $this->include('podcast/_partials/header') ?>
|
||||
|
||||
<main class="flex-shrink-0 w-full min-w-0 sm:w-auto sm:flex-1 sm:flex-shrink">
|
||||
<nav class="sticky top-0 left-0 z-50 flex items-center w-full h-12 px-2 py-1 sm:hidden bg-pine-800">
|
||||
<button
|
||||
data-toggle="main-header"
|
||||
data-toggle-class="sticky -translate-x-full"
|
||||
class="flex-shrink-0 mr-3 overflow-hidden rounded-full focus:ring-2 focus:outline-none focus:ring-pine-50">
|
||||
<img src="<?= $podcast->image
|
||||
->thumbnail_url ?>" alt="<?= $podcast->title ?>" class="h-10"/>
|
||||
</button>
|
||||
<p class="flex flex-col flex-1 min-w-0 mr-2 text-white">
|
||||
<span class="text-sm font-semibold truncate"><?= $podcast->title ?></span>
|
||||
<span class="text-xs">@<?= $podcast->handle ?></span>
|
||||
</p>
|
||||
<?= anchor_popup(
|
||||
route_to('follow', $podcast->handle),
|
||||
icon(
|
||||
'social/castopod',
|
||||
'mr-2 text-xl text-pink-200 group-hover:text-pink-50',
|
||||
) . lang('Podcast.follow'),
|
||||
[
|
||||
'width' => 420,
|
||||
'height' => 620,
|
||||
'class' =>
|
||||
'group inline-flex mr-2 items-center px-3 py-1 text-xs tracking-wider font-semibold text-white uppercase rounded-full shadow focus:outline-none focus:ring bg-rose-600',
|
||||
],
|
||||
) ?>
|
||||
<button
|
||||
data-toggle="main-sidebar"
|
||||
data-toggle-class="translate-x-full"
|
||||
data-toggle-body-class="-ml-64"
|
||||
class="p-2 text-xl rounded-full focus:outline-none focus:ring-2 focus:ring-pine-600 text-pine-200 hover:text-pine-50"><?= icon(
|
||||
'menu',
|
||||
) ?><span class="sr-only"><?= lang('Podcast.toggle_podcast_sidebar') ?></span></button>
|
||||
</nav>
|
||||
<?= $this->renderSection('content') ?>
|
||||
</main>
|
||||
|
||||
<?= $this->include('podcast/_partials/sidebar') ?>
|
||||
|
||||
<button data-toggle="main-sidebar" data-toggle-class="translate-x-full" data-toggle-body-class="-ml-64" class="fixed z-40 hidden p-4 text-xl rounded-full shadow-2xl sm:block lg:hidden bottom-4 left-4 bg-pine-800 focus:outline-none focus:ring-2 focus:ring-pine-600 text-pine-200 hover:text-pine-50"><?= icon(
|
||||
'menu',
|
||||
) ?><span class="sr-only"><?= lang(
|
||||
'Podcast.toggle_podcast_sidebar',
|
||||
) ?></span></button>
|
||||
|
||||
<!-- Funding links modal -->
|
||||
<div id="funding-links" class="fixed top-0 left-0 z-50 flex items-center justify-center hidden w-screen h-screen">
|
||||
<div class="absolute w-full h-full bg-pine-800 bg-opacity-90" role="button" data-toggle="funding-links" data-toggle-class="hidden" aria-label="<?= lang(
|
||||
'Common.close',
|
||||
) ?>"></div>
|
||||
<div class="z-10 w-full max-w-xl bg-white rounded-lg shadow-2xl">
|
||||
<div class="flex justify-between px-4 py-2 border-b">
|
||||
<h3 class="self-center text-lg"><?= lang(
|
||||
'Podcast.funding_links',
|
||||
[
|
||||
'podcastTitle' => $podcast->title,
|
||||
],
|
||||
) ?></h3>
|
||||
<button data-toggle="funding-links" data-toggle-class="hidden" aria-label="<?= lang(
|
||||
'Common.close',
|
||||
) ?>" class="self-start p-1 text-2xl">
|
||||
<?= icon('close') ?>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-col items-start p-4 space-y-4">
|
||||
<?php foreach (
|
||||
$podcast->fundingPlatforms
|
||||
as $fundingPlatform
|
||||
): ?>
|
||||
<?php if ($fundingPlatform->is_visible): ?>
|
||||
<a href="<?= $fundingPlatform->link_url ?>" title="<?= $fundingPlatform->link_content ?>" target="_blank" rel="noopener noreferrer" class="inline-flex items-center font-semibold text-pine-900">
|
||||
<?= icon(
|
||||
$fundingPlatform->type .
|
||||
'/' .
|
||||
$fundingPlatform->slug,
|
||||
'text-2xl text-gray-400 mr-2',
|
||||
) . $fundingPlatform->link_url ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
|
@ -1,23 +0,0 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
$navigationItems = [
|
||||
[
|
||||
'uri' => route_to('podcast-activity', $podcast->handle),
|
||||
'label' => lang('Podcast.activity'),
|
||||
],
|
||||
[
|
||||
'uri' => route_to('podcast-episodes', $podcast->handle),
|
||||
'label' => lang('Podcast.episodes'),
|
||||
],
|
||||
[
|
||||
'uri' => route_to('podcast-about', $podcast->handle),
|
||||
'label' => lang('Podcast.about'),
|
||||
],
|
||||
]
|
||||
?>
|
||||
<nav class="flex gap-4 px-8">
|
||||
<?php foreach ($navigationItems as $item): ?>
|
||||
<?php $isActive = url_is($item['uri']); ?>
|
||||
<a href="<?= $item['uri'] ?>" class="px-4 py-1 font-semibold uppercase border-b-4<?= $isActive ? ' border-b-4 text-pine-500 border-pine-500' : ' text-gray-500 hover:text-gray-900 hover:border-gray-200 border-transparent' ?>"><?= $item['label'] ?></a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
|
@ -1,19 +0,0 @@
|
|||
<footer>
|
||||
<button class="inline-flex items-center opacity-50 cursor-not-allowed hover:underline" title="<?= lang(
|
||||
'Comment.like',
|
||||
[
|
||||
'numberOfLikes' => $comment->likes_count,
|
||||
],
|
||||
) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . $comment->likes_count ?></button>
|
||||
<?php if ($comment->replies_count): ?>
|
||||
<?= anchor(
|
||||
route_to('comment', $podcast->handle, $episode->slug, $comment->id),
|
||||
icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', [
|
||||
'numberOfReplies' => $comment->replies_count,
|
||||
]),
|
||||
[
|
||||
'class' => 'inline-flex items-center text-xs hover:underline',
|
||||
]
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
</footer>
|
|
@ -1,22 +0,0 @@
|
|||
<footer>
|
||||
<form action="<?= route_to('comment-attempt-like', interact_as_actor()->username, $comment->episode->slug, $comment->id) ?>" method="POST" class="flex items-center gap-x-4">
|
||||
<button type="submit" name="action" class="inline-flex items-center hover:underline group" title="<?= lang(
|
||||
'Comment.likes',
|
||||
[
|
||||
'numberOfLikes' => $comment->likes_count,
|
||||
],
|
||||
) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . $comment->likes_count ?></button>
|
||||
<Button uri="<?= route_to('comment', $podcast->handle, $comment->episode->slug, $comment->id) ?>" size="small"><?= lang('Comment.reply') ?></Button>
|
||||
</form>
|
||||
<?php if ($comment->replies_count): ?>
|
||||
<?= anchor(
|
||||
route_to('comment', $podcast->handle, $comment->episode->slug, $comment->id),
|
||||
icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', [
|
||||
'numberOfReplies' => $comment->replies_count,
|
||||
]),
|
||||
[
|
||||
'class' => 'inline-flex items-center text-xs hover:underline',
|
||||
]
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
</footer>
|
|
@ -1,19 +0,0 @@
|
|||
<footer>
|
||||
<button class="inline-flex items-center opacity-50 cursor-not-allowed hover:underline" title="<?= lang(
|
||||
'Comment.like',
|
||||
[
|
||||
'numberOfLikes' => $comment->likes_count,
|
||||
],
|
||||
) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . $comment->likes_count ?></button>
|
||||
<?php if ($comment->replies_count): ?>
|
||||
<?= anchor(
|
||||
route_to('post', $podcast->handle, $comment->id),
|
||||
icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', [
|
||||
'numberOfReplies' => $comment->replies_count,
|
||||
]),
|
||||
[
|
||||
'class' => 'inline-flex items-center text-xs hover:underline',
|
||||
]
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
</footer>
|
|
@ -1,22 +0,0 @@
|
|||
<footer>
|
||||
<form action="<?= route_to('post-attempt-action', interact_as_actor()->username, $comment->id) ?>" method="POST" class="flex items-center gap-x-4">
|
||||
<button type="submit" name="action" value="favourite" class="inline-flex items-center hover:underline group" title="<?= lang(
|
||||
'Comment.likes',
|
||||
[
|
||||
'numberOfLikes' => $comment->likes_count,
|
||||
],
|
||||
) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . $comment->likes_count ?></button>
|
||||
<Button uri="<?= route_to('post', $podcast->handle, $comment->id) ?>" size="small"><?= lang('Comment.reply') ?></Button>
|
||||
</form>
|
||||
<?php if ($comment->replies_count): ?>
|
||||
<?= anchor(
|
||||
route_to('post', $podcast->handle, $comment->id),
|
||||
icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', [
|
||||
'numberOfReplies' => $comment->replies_count,
|
||||
]),
|
||||
[
|
||||
'class' => 'inline-flex items-center text-xs hover:underline',
|
||||
]
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
</footer>
|
|
@ -1,26 +0,0 @@
|
|||
<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 text-sm">
|
||||
<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>
|
||||
<?php if ($comment->is_from_post): ?>
|
||||
<?= $this->include('podcast/_partials/comment_actions_from_post_authenticated') ?>
|
||||
<?php else: ?>
|
||||
<?= $this->include('podcast/_partials/comment_actions_authenticated') ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</article>
|
|
@ -1,38 +0,0 @@
|
|||
<article class="relative z-10 flex w-full p-4 bg-white shadow 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 text-sm">
|
||||
<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>
|
||||
<?php if ($comment->is_from_post): ?>
|
||||
<?= $this->include('podcast/_partials/comment_actions_from_post') ?>
|
||||
<?php else: ?>
|
||||
<footer>
|
||||
<button class="inline-flex items-center opacity-50 cursor-not-allowed" title="<?= lang(
|
||||
'Comment.likes',
|
||||
[
|
||||
'numberOfLikes' => $comment->likes_count,
|
||||
],
|
||||
) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . lang(
|
||||
'Comment.likes',
|
||||
[
|
||||
'numberOfLikes' => $comment->likes_count,
|
||||
],
|
||||
) ?></button>
|
||||
</footer>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</article>
|
|
@ -1,20 +0,0 @@
|
|||
<footer class="flex items-center gap-x-4">
|
||||
<button type="submit" name="action" class="inline-flex items-center opacity-50 cursor-not-allowed" disabled="disabled" title="<?= lang(
|
||||
'Comment.likes',
|
||||
[
|
||||
'numberOfLikes' => $reply->likes_count,
|
||||
],
|
||||
) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . $reply->likes_count ?></button>
|
||||
<?php if ($reply->replies_count): ?>
|
||||
<?= anchor(
|
||||
route_to('comment', $podcast->handle, $episode->slug, $reply->id),
|
||||
icon('chat', 'text-2xl mr-1 text-gray-400') . $reply->replies_count,
|
||||
[
|
||||
'class' => 'inline-flex items-center hover:underline',
|
||||
'title' => lang('Comment.replies', [
|
||||
'numberOfReplies' => $reply->replies_count,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
</footer>
|
|
@ -1,11 +0,0 @@
|
|||
<footer>
|
||||
<form action="<?= route_to('comment-attempt-like', interact_as_actor()->username, $episode->slug, $reply->id) ?>" method="POST" class="flex items-center gap-x-4">
|
||||
<button type="submit" name="action" class="inline-flex items-center hover:underline group" title="<?= lang(
|
||||
'Comment.likes',
|
||||
[
|
||||
'numberOfLikes' => $reply->likes_count,
|
||||
],
|
||||
) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . $reply->likes_count ?></button>
|
||||
<Button uri="<?= route_to('comment', $podcast->handle, $episode->slug, $reply->id) ?>" size="small"><?= lang('Comment.reply') ?></Button>
|
||||
</form>
|
||||
</footer>
|
|
@ -1,19 +0,0 @@
|
|||
<article class="flex px-6 py-4 bg-gray-50">
|
||||
<img src="<?= $reply->actor->avatar_image_url ?>" alt="<?= $reply->actor
|
||||
->display_name ?>" class="w-12 h-12 mr-4 rounded-full ring-gray-50 ring-2" />
|
||||
<div class="flex flex-col flex-1 min-w-0">
|
||||
<header class="flex items-center mb-2">
|
||||
<a href="<?= $reply->actor
|
||||
->uri ?>" class="mr-2 text-base font-semibold truncate hover:underline" <?= $reply
|
||||
->actor->is_local
|
||||
? ''
|
||||
: 'target="_blank" rel="noopener noreferrer"' ?>><?= $reply->actor
|
||||
->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($reply->created_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?>
|
||||
</header>
|
||||
<p class="mb-2 post-content"><?= $reply->message_html ?></p>
|
||||
<?= $this->include('podcast/_partials/comment_reply_actions_authenticated') ?>
|
||||
</div>
|
||||
</article>
|
|
@ -1,10 +0,0 @@
|
|||
<?= $this->include('podcast/_partials/comment_card') ?>
|
||||
<div class="-mt-2 overflow-hidden border-b border-l border-r post-replies rounded-b-xl">
|
||||
|
||||
<?php foreach ($comment->replies as $reply): ?>
|
||||
<?= view('podcast/_partials/comment_reply', [
|
||||
'reply' => $reply,
|
||||
]) ?>
|
||||
<?php endforeach; ?>
|
||||
|
||||
</div>
|
|
@ -1,25 +0,0 @@
|
|||
<?= $this->include('podcast/_partials/comment_card_authenticated') ?>
|
||||
<div class="-mt-2 overflow-hidden border-b border-l border-r post-replies rounded-b-xl">
|
||||
<form action="<?= route_to('comment-attempt-reply', $podcast->id, $episode->id, $comment->id) ?>" method="POST" class="flex px-6 pt-8 pb-4 bg-gray-50">
|
||||
<img src="<?= interact_as_actor()
|
||||
->avatar_image_url ?>" alt="<?= interact_as_actor()
|
||||
->display_name ?>" class="w-12 h-12 mr-4 rounded-full ring-gray-50 ring-2" />
|
||||
<div class="flex flex-col flex-1">
|
||||
<Forms.Textarea
|
||||
name="message"
|
||||
required="true"
|
||||
class="w-full mb-4"
|
||||
placeholder="<?= lang('Comment.form.reply_to_placeholder', [
|
||||
'actorUsername' => $comment->actor->username,
|
||||
]) ?>"
|
||||
rows="1" />
|
||||
<Button variant="primary" size="small" type="submit" name="action" value="reply"><?= lang('Comment.form.submit_reply') ?></Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php foreach ($comment->replies as $reply): ?>
|
||||
<?= view('podcast/_partials/comment_reply_authenticated', [
|
||||
'reply' => $reply,
|
||||
]) ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
|
@ -1,43 +0,0 @@
|
|||
<article class="w-full mb-4 bg-white rounded-lg shadow">
|
||||
<div class="flex p-4">
|
||||
<div class="relative mr-2">
|
||||
<time class="absolute px-1 text-xs font-semibold text-white rounded bottom-2 right-2 bg-black/50" datetime="PT<?= $episode->audio_file_duration ?>S">
|
||||
<?= format_duration(
|
||||
$episode->audio_file_duration,
|
||||
) ?>
|
||||
</time>
|
||||
<img loading="lazy" src="<?= $episode->image
|
||||
->thumbnail_url ?>" alt="<?= $episode->title ?>" class="object-cover w-20 h-20 rounded-lg" />
|
||||
</div>
|
||||
<div class="flex flex-col flex-1">
|
||||
<a class="flex justify-between text-sm" href="<?= $episode->link ?>">
|
||||
<h2 class="flex-1 font-semibold hover:underline">
|
||||
<?= episode_numbering(
|
||||
$episode->number,
|
||||
$episode->season_number,
|
||||
'text-xs font-semibold text-gray-600',
|
||||
true,
|
||||
) ?>
|
||||
<span class="mx-1">-</span>
|
||||
<?= $episode->title ?>
|
||||
</h2>
|
||||
<?= relative_time($episode->published_at, 'text-xs whitespace-nowrap') ?>
|
||||
</a>
|
||||
<div class="flex mt-auto gap-x-4">
|
||||
<?= play_episode_button($episode->id, $episode->image->thumbnail_url, $episode->title, $podcast->title, $episode->audio_file_web_url, $episode->audio_file_mimetype, 'mt-auto') ?>
|
||||
<?= anchor(
|
||||
route_to('episode', $podcast->handle, $episode->slug),
|
||||
icon('chat', 'text-xl mr-1 text-gray-400') .
|
||||
$episode->comments_count,
|
||||
[
|
||||
'class' =>
|
||||
'inline-flex items-center hover:underline',
|
||||
'title' => lang('Episode.number_of_comments', [
|
||||
'numberOfComments' => $episode->comments_count,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
|
@ -1,26 +0,0 @@
|
|||
<div class="flex">
|
||||
<div class="relative">
|
||||
<time class="absolute px-1 text-sm font-semibold text-white bg-black/50 bottom-2 right-2" datetime="PT<?= $episode->audio_file_duration ?>S">
|
||||
<?= format_duration($episode->audio_file_duration) ?>
|
||||
</time>
|
||||
<img
|
||||
src="<?= $episode->image->thumbnail_url ?>"
|
||||
alt="<?= $episode->title ?>" class="w-24 h-24"/>
|
||||
</div>
|
||||
<div class="flex flex-col flex-1 px-4 py-2 border-t border-b">
|
||||
<a href="<?= $episode->link ?>" class="flex justify-between flex-1">
|
||||
<div class="flex items-baseline font-semibold">
|
||||
<?= episode_numbering(
|
||||
$episode->number,
|
||||
$episode->season_number,
|
||||
'text-xs font-semibold text-gray-600',
|
||||
true,
|
||||
) ?>
|
||||
<span class="mx-1">-</span>
|
||||
<?= $episode->title ?>
|
||||
</div>
|
||||
<?= relative_time($episode->published_at, 'text-xs whitespace-nowrap') ?>
|
||||
</a>
|
||||
<?= play_episode_button($episode->id, $episode->image->thumbnail_url, $episode->title, $episode->podcast->title, $episode->audio_file_web_url, $episode->audio_file_mimetype, 'mt-auto') ?>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,39 @@
|
|||
<div id="funding-links" class="fixed top-0 left-0 z-50 flex items-center justify-center hidden w-screen h-screen">
|
||||
<div
|
||||
class="absolute w-full h-full bg-pine-800/75"
|
||||
role="button"
|
||||
data-toggle="funding-links"
|
||||
data-toggle-class="hidden"
|
||||
aria-label="<?= lang('Common.close') ?>"></div>
|
||||
<div class="z-10 w-full max-w-xl bg-white rounded-lg shadow-2xl">
|
||||
<div class="flex justify-between px-4 py-2 border-b">
|
||||
<h3 class="self-center text-lg"><?= lang('Podcast.funding_links', [
|
||||
'podcastTitle' => $podcast->title,
|
||||
]) ?></h3>
|
||||
<button
|
||||
data-toggle="funding-links"
|
||||
data-toggle-class="hidden"
|
||||
aria-label="<?= lang('Common.close') ?>"
|
||||
class="self-start p-1 text-2xl"><?= icon('close') ?></button>
|
||||
</div>
|
||||
<div class="flex flex-col items-start p-4 space-y-4">
|
||||
<?php foreach ($podcast->fundingPlatforms as $fundingPlatform): ?>
|
||||
<?php if ($fundingPlatform->is_visible): ?>
|
||||
<a
|
||||
href="<?= $fundingPlatform->link_url ?>"
|
||||
title="<?= $fundingPlatform->link_content ?>"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex items-center font-semibold text-pine-900">
|
||||
<?= icon(
|
||||
$fundingPlatform->type .
|
||||
'/' .
|
||||
$fundingPlatform->slug,
|
||||
'mr-2',
|
||||
) . $fundingPlatform->link_url ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,58 +0,0 @@
|
|||
<header id="main-header" class="fixed top-0 left-0 flex-col flex-shrink-0 h-screen transform -translate-x-full sm:left-auto sm:-translate-x-0 sm:sticky w-80 sm:w-64 lg:w-80 xl:w-112 sm:flex">
|
||||
<img src="<?= $podcast->actor
|
||||
->cover_image_url ?>" alt="" class="object-cover w-full h-48 bg-pine-800"/>
|
||||
<div class="flex items-center justify-between px-4 py-2 mb-4 lg:px-6 -mt-14 lg:-mt-16 xl:-mt-20">
|
||||
<img src="<?= $podcast->image
|
||||
->thumbnail_url ?>" alt="<?= $podcast->title ?>" class="h-24 rounded-full shadow-xl xl:h-36 lg:h-28 ring-4 ring-pine-50" />
|
||||
<?= anchor_popup(
|
||||
route_to('follow', $podcast->handle),
|
||||
icon(
|
||||
'social/castopod',
|
||||
'mr-2 text-xl text-pink-200 group-hover:text-pink-50',
|
||||
) . lang('Podcast.follow'),
|
||||
[
|
||||
'width' => 420,
|
||||
'height' => 620,
|
||||
'class' =>
|
||||
'group inline-flex items-center px-4 py-2 text-xs tracking-wider font-semibold text-white uppercase rounded-full shadow focus:outline-none focus:ring bg-rose-600',
|
||||
],
|
||||
) ?>
|
||||
</div>
|
||||
<div class="px-6">
|
||||
<h1 class="inline-flex items-center text-2xl font-bold leading-none font-display"><?= $podcast->title .
|
||||
($podcast->parental_advisory === 'explicit'
|
||||
? '<span class="px-1 ml-2 text-xs font-semibold leading-tight tracking-wider text-gray-600 uppercase border-2 border-gray-500">' .
|
||||
lang('Common.explicit') .
|
||||
'</span>'
|
||||
: '') ?></h1>
|
||||
<p class="mb-4 font-semibold text-gray-600">@<?= $podcast->handle ?></p>
|
||||
<div class="mb-2"><?= $podcast->description_html ?></div>
|
||||
<?= location_link($podcast->location, 'text-sm mb-4') ?>
|
||||
<div class="mb-6 space-x-4">
|
||||
<span class="px-2 py-1 text-sm text-gray-800 bg-gray-200">
|
||||
<?= lang(
|
||||
'Podcast.category_options.' . $podcast->category->code,
|
||||
) ?>
|
||||
</span>
|
||||
<?php foreach ($podcast->other_categories as $other_category): ?>
|
||||
<span class="px-2 py-1 text-sm text-gray-800 bg-gray-200">
|
||||
<?= lang(
|
||||
'Podcast.category_options.' . $other_category->code,
|
||||
) ?>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?= person_list($podcast->persons, 'mb-6') ?>
|
||||
<div class="space-x-4">
|
||||
<a href="#" class="hover:underline"><?= lang('Podcast.followers', [
|
||||
'numberOfFollowers' => $podcast->actor->followers_count,
|
||||
]) ?></a>
|
||||
<a href="<?= route_to(
|
||||
'podcast-activity',
|
||||
$podcast->handle,
|
||||
) ?>" class="hover:underline"><?= lang('Podcast.posts', [
|
||||
'numberOfPosts' => $podcast->actor->posts_count,
|
||||
]) ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
|
@ -0,0 +1,24 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
$navigationItems = [
|
||||
[
|
||||
'uri' => route_to('podcast-activity', $podcast->handle),
|
||||
'label' => lang('Podcast.activity'),
|
||||
],
|
||||
[
|
||||
'uri' => route_to('podcast-episodes', $podcast->handle),
|
||||
'label' => lang('Podcast.episodes'),
|
||||
],
|
||||
[
|
||||
'uri' => route_to('podcast-about', $podcast->handle),
|
||||
'label' => lang('Podcast.about'),
|
||||
],
|
||||
]
|
||||
?>
|
||||
<nav class="sticky z-40 flex col-start-2 px-4 pt-8 bg-white shadow gap-x-2 md:gap-x-4 md:px-8 -top-8 md:-top-12 md:rounded-b-xl md:pt-12 ">
|
||||
<?php foreach ($navigationItems as $item): ?>
|
||||
<?php $isActive = url_is($item['uri']); ?>
|
||||
<a href="<?= $item['uri'] ?>" class="px-4 py-1 text-sm font-semibold uppercase border-b-4<?= $isActive ? ' border-b-4 text-pine-500 border-pine-500' : ' text-gray-500 hover:text-gray-900 hover:border-gray-200 border-transparent' ?>"><?= $item['label'] ?></a>
|
||||
<?php endforeach; ?>
|
||||
<button type="button" class="p-2 ml-auto rotate-180 rounded-full sm:hidden focus:outline-none focus:ring-2 focus:ring-pine-500 focus:ring-offset-2 focus:ring-offset-pine-100"><?= icon('menu') ?></button>
|
||||
</nav>
|
|
@ -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="<?= $post->actor
|
||||
->avatar_image_url ?>" alt="<?= $post->actor->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
|
||||
<div class="flex flex-col min-w-0">
|
||||
<a href="<?= $post->actor
|
||||
->uri ?>" class="flex items-baseline hover:underline" <?= $post
|
||||
->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>
|
|
@ -1,36 +0,0 @@
|
|||
<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,92 +0,0 @@
|
|||
<footer class="px-6 py-3">
|
||||
<form action="<?= route_to(
|
||||
'post-attempt-action',
|
||||
interact_as_actor()
|
||||
->username,
|
||||
$post->id,
|
||||
) ?>" method="POST" class="flex justify-around">
|
||||
<?= csrf_field() ?>
|
||||
<?= 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,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
<button type="submit" name="action" value="reblog" class="inline-flex items-center hover:underline" title="<?= lang(
|
||||
'Post.reblogs',
|
||||
[
|
||||
'numberOfReblogs' => $post->reblogs_count,
|
||||
],
|
||||
) ?>"><?= icon('repeat', 'text-2xl mr-1 text-gray-400') .
|
||||
$post->reblogs_count ?></button>
|
||||
<button type="submit" name="action" value="favourite" class="inline-flex items-center hover:underline" title="<?= lang(
|
||||
'Post.favourites',
|
||||
[
|
||||
'numberOfFavourites' => $post->favourites_count,
|
||||
],
|
||||
) ?>"><?= icon('heart', 'text-2xl mr-1 text-gray-400') .
|
||||
$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="<?= $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('post', $podcast->handle, $post->id),
|
||||
lang('Post.expand'),
|
||||
[
|
||||
'class' => 'px-4 py-1 hover:bg-gray-100',
|
||||
],
|
||||
) ?>
|
||||
<form action="<?= route_to(
|
||||
'post-attempt-block-actor',
|
||||
interact_as_actor()
|
||||
->username,
|
||||
$post->id,
|
||||
) ?>" method="POST">
|
||||
<?= csrf_field() ?>
|
||||
<button class="w-full px-4 py-1 text-left hover:bg-gray-100"><?= lang(
|
||||
'Post.block_actor',
|
||||
[
|
||||
'actorUsername' => $post->actor->username,
|
||||
],
|
||||
) ?></button>
|
||||
</form>
|
||||
<form action="<?= route_to(
|
||||
'post-attempt-block-domain',
|
||||
interact_as_actor()
|
||||
->username,
|
||||
$post->id,
|
||||
) ?>" method="POST">
|
||||
<?= csrf_field() ?>
|
||||
<button class="w-full px-4 py-1 text-left hover:bg-gray-100"><?= lang(
|
||||
'Post.block_domain',
|
||||
[
|
||||
'actorDomain' => $post->actor->domain,
|
||||
],
|
||||
) ?></button>
|
||||
</form>
|
||||
<?php if ($post->actor->is_local): ?>
|
||||
<hr class="my-2" />
|
||||
<form action="<?= route_to(
|
||||
'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(
|
||||
'Post.delete',
|
||||
) ?></button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
</footer>
|
|
@ -1,27 +0,0 @@
|
|||
<?= $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('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',
|
||||
'width' => 420,
|
||||
'height' => 620,
|
||||
],
|
||||
) ?>
|
||||
</div>
|
||||
|
||||
|
||||
<?php if ($post->has_replies): ?>
|
||||
<?php foreach ($post->replies as $reply): ?>
|
||||
<?= view('podcast/_partials/reply', [
|
||||
'reply' => $reply,
|
||||
]) ?>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
|
@ -1,27 +0,0 @@
|
|||
<?= $this->include('podcast/_partials/post_authenticated') ?>
|
||||
<div class="-mt-2 overflow-hidden border-b border-l border-r post-replies rounded-b-xl">
|
||||
<form action="<?= route_to('post-attempt-action', interact_as_actor()->username, $post->id) ?>" method="POST" class="flex px-6 pt-8 pb-4 bg-gray-50" >
|
||||
<img src="<?= interact_as_actor()
|
||||
->avatar_image_url ?>" alt="<?= interact_as_actor()
|
||||
->display_name ?>" class="w-12 h-12 mr-4 rounded-full ring-gray-50 ring-2" />
|
||||
<div class="flex flex-col flex-1">
|
||||
<Forms.Textarea
|
||||
name="message"
|
||||
class="w-full mb-4"
|
||||
required="true"
|
||||
placeholder="<?= lang('Post.form.reply_to_placeholder', [
|
||||
'actorUsername' => $post->actor->username,
|
||||
]) ?>"
|
||||
rows="1" />
|
||||
<Button variant="primary" size="small" type="submit" name="action" value="reply" class="self-end"><?= lang('Post.form.submit_reply') ?></Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if ($post->has_replies): ?>
|
||||
<?php foreach ($post->replies as $reply): ?>
|
||||
<?= view('podcast/_partials/reply_authenticated', [
|
||||
'reply' => $reply,
|
||||
]) ?>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
|
@ -1,43 +0,0 @@
|
|||
<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('Post.actor_shared', [
|
||||
'actor' => $post->actor->display_name,
|
||||
]) ?></p>
|
||||
<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,36 +0,0 @@
|
|||
<footer class="mt-2 space-x-6 text-sm">
|
||||
<?= anchor(
|
||||
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('Post.replies', [
|
||||
'numberOfReplies' => $reply->replies_count,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
<?= anchor_popup(
|
||||
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('Post.reblogs', [
|
||||
'numberOfReblogs' => $reply->reblogs_count,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
<?= anchor_popup(
|
||||
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('Post.favourites', [
|
||||
'numberOfFavourites' => $reply->favourites_count,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
</footer>
|
|
@ -1,92 +0,0 @@
|
|||
<footer class="mt-2 text-sm">
|
||||
<form action="<?= route_to(
|
||||
'post-attempt-action',
|
||||
interact_as_actor()
|
||||
->username,
|
||||
$reply->id,
|
||||
) ?>" method="POST" class="flex items-start">
|
||||
<?= csrf_field() ?>
|
||||
<?= anchor(
|
||||
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('Post.replies', [
|
||||
'numberOfReplies' => $reply->replies_count,
|
||||
]),
|
||||
],
|
||||
) ?>
|
||||
<button type="submit" name="action" value="reblog" class="inline-flex items-center mr-6 hover:underline" title="<?= lang(
|
||||
'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(
|
||||
'Post.favourites',
|
||||
[
|
||||
'numberOfFavourites' => $reply->favourites_count,
|
||||
],
|
||||
) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400') .
|
||||
$reply->favourites_count ?></button>
|
||||
<button id="<?= $reply->id .
|
||||
'-more-dropdown' ?>" type="button" class="text-xl text-gray-500 outline-none focus:ring" data-dropdown="button" data-dropdown-target="<?= $reply->id .
|
||||
'-more-dropdown-menu' ?>" aria-label="<?= lang(
|
||||
'Common.more',
|
||||
) ?>" aria-haspopup="true" aria-expanded="false"><?= icon('more') ?>
|
||||
</button>
|
||||
</form>
|
||||
<nav id="<?= $reply->id .
|
||||
'-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('post', $podcast->handle, $reply->id),
|
||||
lang('Post.expand'),
|
||||
[
|
||||
'class' => 'px-4 py-1 hover:bg-gray-100',
|
||||
],
|
||||
) ?>
|
||||
<form action="<?= route_to(
|
||||
'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(
|
||||
'Post.block_actor',
|
||||
[
|
||||
'actorUsername' => $reply->actor->username,
|
||||
],
|
||||
) ?></button>
|
||||
</form>
|
||||
<form action="<?= route_to(
|
||||
'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(
|
||||
'Post.block_domain',
|
||||
[
|
||||
'actorDomain' => $reply->actor->domain,
|
||||
],
|
||||
) ?></button>
|
||||
</form>
|
||||
<?php if ($reply->actor->is_local): ?>
|
||||
<hr class="my-2" />
|
||||
<form action="<?= route_to(
|
||||
'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(
|
||||
'Post.delete',
|
||||
) ?></button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
</footer>
|
|
@ -1,24 +0,0 @@
|
|||
<article class="flex px-6 py-4 bg-gray-50">
|
||||
<img src="<?= $reply->actor->avatar_image_url ?>" alt="<?= $reply->actor
|
||||
->display_name ?>" class="w-12 h-12 mr-4 rounded-full ring-gray-50 ring-2" />
|
||||
<div class="flex flex-col flex-1 min-w-0">
|
||||
<header class="flex items-center mb-2">
|
||||
<a href="<?= $reply->actor
|
||||
->uri ?>" class="mr-2 text-base font-semibold truncate hover:underline" <?= $reply
|
||||
->actor->is_local
|
||||
? ''
|
||||
: 'target="_blank" rel="noopener noreferrer"' ?>><?= $reply->actor
|
||||
->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($post->published_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?>
|
||||
</header>
|
||||
<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,
|
||||
]) ?>
|
||||
<?php endif; ?>
|
||||
<?= $this->include('podcast/_partials/reply_actions_authenticated') ?>
|
||||
</div>
|
||||
</article>
|
|
@ -1,65 +1,49 @@
|
|||
<aside id="main-sidebar" class="fixed top-0 right-0 flex flex-col items-start flex-shrink-0 w-64 h-screen px-6 py-4 overflow-y-auto transform translate-x-full lg:sticky lg:translate-x-0">
|
||||
<?php if (
|
||||
in_array(true, array_column($podcast->fundingPlatforms, 'is_visible'), true)
|
||||
): ?>
|
||||
<h2 class="mb-2 text-sm font-semibold"><?= lang(
|
||||
'Podcast.sponsor_title',
|
||||
) ?></h2>
|
||||
<button
|
||||
class="inline-flex items-center px-2 py-1 mb-8 text-sm font-semibold text-gray-600 border rounded-full shadow-sm focus:outline-none focus:ring focus:ring-pine-600 hover:bg-rose-200 hover:text-gray-800 border-rose-600 bg-rose-100"
|
||||
data-toggle="funding-links"
|
||||
data-toggle-class="hidden"><?= icon('heart', 'mr-2 text-rose-600') .
|
||||
lang('Podcast.sponsor') ?></button>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (
|
||||
in_array(true, array_column($podcast->socialPlatforms, 'is_visible'), true)
|
||||
): ?>
|
||||
<h2 class="mb-2 text-sm font-semibold"> <?= lang('Podcast.find_on', [
|
||||
'podcastTitle' => $podcast->title,
|
||||
]) ?></h2>
|
||||
<div class="grid items-center justify-center grid-cols-5 gap-3 mb-8">
|
||||
<?php foreach ($podcast->socialPlatforms as $socialPlatform): ?>
|
||||
<?php if ($socialPlatform->is_visible): ?>
|
||||
<?= anchor(
|
||||
$socialPlatform->link_url,
|
||||
icon($socialPlatform->type . '/' . $socialPlatform->slug),
|
||||
[
|
||||
'class' => 'text-2xl text-gray-500 hover:text-gray-700',
|
||||
'target' => '_blank',
|
||||
'rel' => 'noopener noreferrer',
|
||||
'data-toggle' => 'tooltip',
|
||||
'data-placement' => 'bottom',
|
||||
'title' => $socialPlatform->label,
|
||||
],
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h2 class="mb-2 text-sm font-semibold"><?= lang('Podcast.listen_on') ?></h2>
|
||||
<div class="grid items-center justify-center grid-cols-5 gap-3 mb-8">
|
||||
<?= anchor(route_to('podcast_feed', $podcast->handle), icon('rss'), [
|
||||
'class' =>
|
||||
'bg-yellow-500 text-xl text-yellow-900 hover:bg-yellow-600 w-8 h-8 inline-flex items-center justify-center rounded-lg',
|
||||
'target' => '_blank',
|
||||
'rel' => 'noopener noreferrer',
|
||||
'data-toggle' => 'tooltip',
|
||||
'data-placement' => 'bottom',
|
||||
'title' => lang('Podcast.feed'),
|
||||
]) ?>
|
||||
<?php foreach ($podcast->podcastingPlatforms as $podcastingPlatform): ?>
|
||||
<?php if ($podcastingPlatform->is_visible): ?>
|
||||
<aside id="podcast-sidebar" class="sticky hidden col-span-1 sm:block top-12">
|
||||
<div class="absolute z-0 w-full h-full sm:hidden bg-pine-800/50"></div>
|
||||
<div class="z-10 bg-pine-50">
|
||||
<a href="<?= route_to('podcast_feed', $podcast->handle) ?>" class="inline-flex items-center mb-6 text-sm font-semibold text-pine-800 group" target="_blank" rel="noopener noreferrer">
|
||||
<?= icon('rss', ' mr-2 bg-orange-500 text-xl text-white group-hover:bg-orange-700 p-1 w-6 h-6 inline-flex items-center justify-center rounded-lg') . lang('Podcast.feed') ?>
|
||||
</a>
|
||||
<?php if (
|
||||
in_array(true, array_column($podcast->socialPlatforms, 'is_visible'), true)
|
||||
): ?>
|
||||
<h2 class="mb-2 font-bold font-display text-pine-900"> <?= lang('Podcast.find_on', [
|
||||
'podcastTitle' => $podcast->title,
|
||||
]) ?></h2>
|
||||
<div class="grid items-center justify-center grid-cols-6 gap-3 mb-6">
|
||||
<?php foreach ($podcast->socialPlatforms as $socialPlatform): ?>
|
||||
<?php if ($socialPlatform->is_visible): ?>
|
||||
<?= anchor(
|
||||
$socialPlatform->link_url,
|
||||
icon("{$socialPlatform->type}/{$socialPlatform->slug}"),
|
||||
[
|
||||
'class' => 'text-2xl text-gray-500 hover:text-gray-700 w-8 h-8 items-center inline-flex justify-center',
|
||||
'target' => '_blank',
|
||||
'rel' => 'noopener noreferrer',
|
||||
'data-toggle' => 'tooltip',
|
||||
'data-placement' => 'bottom',
|
||||
'title' => $socialPlatform->label,
|
||||
],
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (
|
||||
in_array(true, array_column($podcast->podcastingPlatforms, 'is_visible'), true)
|
||||
): ?>
|
||||
<h2 class="mb-2 font-bold font-display text-pine-900"><?= lang('Podcast.listen_on') ?></h2>
|
||||
<div class="grid items-center justify-center grid-cols-6 gap-3 mb-6">
|
||||
<?php foreach ($podcast->podcastingPlatforms as $podcastingPlatform): ?>
|
||||
<?php if ($podcastingPlatform->is_visible): ?>
|
||||
<?= anchor(
|
||||
$podcastingPlatform->link_url,
|
||||
icon(
|
||||
$podcastingPlatform->type .
|
||||
'/' .
|
||||
$podcastingPlatform->slug,
|
||||
"{$podcastingPlatform->type}/{$podcastingPlatform->slug}",
|
||||
),
|
||||
[
|
||||
'class' => 'text-2xl text-gray-500 hover:text-gray-700',
|
||||
'class' => 'text-2xl text-gray-500 hover:text-gray-700 w-8 h-8 items-center inline-flex justify-center',
|
||||
'target' => '_blank',
|
||||
'rel' => 'noopener noreferrer',
|
||||
'data-toggle' => 'tooltip',
|
||||
|
@ -67,19 +51,19 @@
|
|||
'title' => $podcastingPlatform->label,
|
||||
],
|
||||
) ?>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<footer class="px-2 py-4 mt-auto text-gray-600 border-t">
|
||||
<div class="container flex flex-col justify-between mx-auto text-xs">
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<footer class="flex flex-col items-center py-2 text-xs text-center text-gray-600 border-t">
|
||||
<?= render_page_links('inline-flex mb-2 flex-wrap gap-y-1') ?>
|
||||
<div class="flex flex-col">
|
||||
<p><?= $podcast->copyright ?></p>
|
||||
<p><?= lang('Common.powered_by', [
|
||||
'castopod' =>
|
||||
'<a class="underline hover:no-underline" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod</a>',
|
||||
'<a class="inline-flex font-semibold text-gray-500 hover:underline" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a>',
|
||||
]) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</aside>
|
||||
</footer>
|
||||
</div>
|
||||
</aside>
|
|
@ -29,7 +29,53 @@
|
|||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<div class="px-2 sm:px-4">
|
||||
<div class="mb-2"><?= $podcast->description_html ?></div>
|
||||
<div class="flex gap-x-4 gap-y-2">
|
||||
<span class="px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-100 border">
|
||||
<?= lang(
|
||||
'Podcast.category_options.' . $podcast->category->code,
|
||||
) ?>
|
||||
</span>
|
||||
<?php foreach ($podcast->other_categories as $other_category): ?>
|
||||
<span class="px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-100 border">
|
||||
<?= lang(
|
||||
'Podcast.category_options.' . $other_category->code,
|
||||
) ?>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center mt-4 gap-x-8">
|
||||
<?php if ($podcast->persons !== []): ?>
|
||||
<button class="flex items-center text-xs font-semibold gap-x-2 hover:underline" data-toggle="persons-list" data-toggle-class="hidden">
|
||||
<div class="inline-flex flex-row-reverse">
|
||||
<?php $i = 0; ?>
|
||||
<?php foreach ($podcast->persons as $person): ?>
|
||||
<img src="<?= $person->image->thumbnail_url ?>" alt="<?= $person->full_name ?>" class="object-cover w-8 h-8 -ml-5 border-2 rounded-full border-pine-100 last:ml-0" />
|
||||
<?php $i++; if ($i === 3) {
|
||||
break;
|
||||
}?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?= lang('Podcast.persons', [
|
||||
'personsCount' => count($podcast->persons),
|
||||
]) ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if ($podcast->location): ?>
|
||||
<?= location_link($podcast->location, 'text-xs font-semibold p-2') ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<?= view('_persons_modal', [
|
||||
'title' => lang('Podcast.persons_list', [
|
||||
'podcastTitle' => $podcast->title,
|
||||
]),
|
||||
'persons' => $podcast->persons,
|
||||
]) ?>
|
||||
|
||||
<?= $this->endSection()
|
||||
?>
|
||||
|
|
|
@ -27,21 +27,52 @@
|
|||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<section class="max-w-2xl mx-auto space-y-8">
|
||||
<section class="w-full">
|
||||
|
||||
<?php foreach ($posts as $post): ?>
|
||||
<?php if ($post->reblog_of_id !== null): ?>
|
||||
<?= view('podcast/_partials/reblog', [
|
||||
'post' => $post->reblog_of_post,
|
||||
'podcast' => $podcast,
|
||||
<?php if (can_user_interact()): ?>
|
||||
|
||||
<form action="<?= route_to('post-attempt-create', interact_as_actor()->username) ?>" method="POST" 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 gap-y-2">
|
||||
<Forms.Textarea
|
||||
name="message"
|
||||
required="true"
|
||||
placeholder="<?= lang('Post.form.message_placeholder') ?>"
|
||||
rows="2" />
|
||||
<Forms.Input
|
||||
name="episode_url"
|
||||
type="url"
|
||||
placeholder="<?= lang('Post.form.episode_url_placeholder') . ' (' . lang('Common.optional') . ')' ?>" />
|
||||
<Button variant="primary" size="small" type="submit" class="self-end" iconRight="send-plane"><?= lang('Post.form.submit') ?></Button>
|
||||
</div>
|
||||
</form>
|
||||
<hr class="my-4 border-2 border-pine-100">
|
||||
|
||||
<?php endif; ?>
|
||||
<div class="flex flex-col gap-y-6">
|
||||
<?php foreach ($posts as $key => $post): ?>
|
||||
<?php if ($post->reblog_of_id !== null): ?>
|
||||
<?= view('post/_partials/reblog', [
|
||||
'index' => $key,
|
||||
'post' => $post->reblog_of_post,
|
||||
'podcast' => $podcast,
|
||||
]) ?>
|
||||
<?php else: ?>
|
||||
<?= view('podcast/_partials/post', [
|
||||
'post' => $post,
|
||||
'podcast' => $podcast,
|
||||
<?php else: ?>
|
||||
<?= view('post/_partials/card', [
|
||||
'index' => $key,
|
||||
'post' => $post,
|
||||
'podcast' => $podcast,
|
||||
]) ?>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<?= $this->endSection() ?>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue