diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 04b8ebfdc54db17dd3e2c5be0899e37cf93fb3a7..9234895e8f73e2cfb305b4398a512a2d052aa848 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -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', diff --git a/app/Controllers/EpisodeCommentController.php b/app/Controllers/EpisodeCommentController.php index 4dcef8619884982d8254a784bb89d4d5911969fc..f64c1db30428f4514c9f7ce0e827133df6188577 100644 --- a/app/Controllers/EpisodeCommentController.php +++ b/app/Controllers/EpisodeCommentController.php @@ -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, ]); diff --git a/app/Controllers/EpisodeController.php b/app/Controllers/EpisodeController.php index 9aacdc684802b06e39f60562a8c8a0ac7b70924e..d5ddd9f780253ad6a7fd74cd790e53e008812772 100644 --- a/app/Controllers/EpisodeController.php +++ b/app/Controllers/EpisodeController.php @@ -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, diff --git a/app/Controllers/PodcastController.php b/app/Controllers/PodcastController.php index 21ce0ed08127fcb2619565ee0fa2783a299bf6c7..f330064d436f8e4861d1a422e5dbe21f95e6f84f 100644 --- a/app/Controllers/PodcastController.php +++ b/app/Controllers/PodcastController.php @@ -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 diff --git a/app/Controllers/PostController.php b/app/Controllers/PostController.php index 184825adca66fc22c76ae8c91e95bf7cb7dafc89..69837feb0d369e143df1502f8af8e0a3eaabd4f5 100644 --- a/app/Controllers/PostController.php +++ b/app/Controllers/PostController.php @@ -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, ]); diff --git a/app/Helpers/components_helper.php b/app/Helpers/components_helper.php index 657eca2d32bea61b05532cc91b344c8cf850db4a..6a85dbf301a53ec4ce6efc77d2497b42934dd78c 100644 --- a/app/Helpers/components_helper.php +++ b/app/Helpers/components_helper.php @@ -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 diff --git a/app/Helpers/misc_helper.php b/app/Helpers/misc_helper.php index 21a7ae5c055b8f4e8f84b9de041d8a60029f2b4a..f988de62aaf73c2269120631aacb517bcd5a9d8f 100644 --- a/app/Helpers/misc_helper.php +++ b/app/Helpers/misc_helper.php @@ -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 diff --git a/app/Language/en/Comment.php b/app/Language/en/Comment.php index aff97d5ee68119c99b468e3443dd676ea4f4bc98..741f78961cab027061ac5014238dec431eaa9127 100644 --- a/app/Language/en/Comment.php +++ b/app/Language/en/Comment.php @@ -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, diff --git a/app/Language/en/Common.php b/app/Language/en/Common.php index debed34830337d3d1d850ca6c2e8eb49f2b3e5b8..3284040fa9e8d5f2e64b475e9106248d082a4839 100644 --- a/app/Language/en/Common.php +++ b/app/Language/en/Common.php @@ -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', ]; diff --git a/app/Language/en/Episode.php b/app/Language/en/Episode.php index 565b7ba1f63cdc5492f090ee8102e1961eac2aaa..74c32b3f920e9e99214a7dbc9ac15106a0f9eb77 100644 --- a/app/Language/en/Episode.php +++ b/app/Language/en/Episode.php @@ -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} diff --git a/app/Language/en/Podcast.php b/app/Language/en/Podcast.php index 0c311ed83257cb8ef4863c809343013eed23b95c..9896a00a4635df6ddf46294791f7a91a1fa009d5 100644 --- a/app/Language/en/Podcast.php +++ b/app/Language/en/Podcast.php @@ -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', ]; diff --git a/app/Language/en/Post.php b/app/Language/en/Post.php index a60ef30fec25eb5bbcd3f54e7fb2efc53e559174..dc348d91bd9edb60d424d7ab4080de37477499fc 100644 --- a/app/Language/en/Post.php +++ b/app/Language/en/Post.php @@ -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, diff --git a/app/Language/fr/Common.php b/app/Language/fr/Common.php index 6fc0c30d3834d424677718a55e20901fedbe3392..c5694c3974a11f72b8a17f4a89eb0293945849e5 100644 --- a/app/Language/fr/Common.php +++ b/app/Language/fr/Common.php @@ -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', ]; diff --git a/app/Language/fr/Episode.php b/app/Language/fr/Episode.php index c6f565c855802ab41d65f060e74a038d91e57d2a..ee8a1e4bd2361557c42eedf8d3e9e372d27e1664 100644 --- a/app/Language/fr/Episode.php +++ b/app/Language/fr/Episode.php @@ -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} diff --git a/app/Language/fr/Podcast.php b/app/Language/fr/Podcast.php index d332a9336297b50ef01b08f0bf21f298f2f69644..d9d0a35d2b964ae79f251227b848acbfecd79c1b 100644 --- a/app/Language/fr/Podcast.php +++ b/app/Language/fr/Podcast.php @@ -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', ]; diff --git a/app/Libraries/CommentObject.php b/app/Libraries/CommentObject.php index 7a4b7ce2d55a663868a1fa245f314593597eb0fb..e7fcac7ffb82a27aed508ab5474b3ac5089e058f 100644 --- a/app/Libraries/CommentObject.php +++ b/app/Libraries/CommentObject.php @@ -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]; } diff --git a/app/Models/EpisodeCommentModel.php b/app/Models/EpisodeCommentModel.php index cd950438e76cbc570c2542419195ff67e3e3d773..3440802513e2cc5c2e83944e4fe6c363b27164d4 100644 --- a/app/Models/EpisodeCommentModel.php +++ b/app/Models/EpisodeCommentModel.php @@ -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; diff --git a/app/Resources/icons/send-plane.svg b/app/Resources/icons/send-plane.svg new file mode 100644 index 0000000000000000000000000000000000000000..74ffbcf9bd622ebb1d66ac500eef82eb2b742b59 --- /dev/null +++ b/app/Resources/icons/send-plane.svg @@ -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> diff --git a/app/Resources/js/app.ts b/app/Resources/js/app.ts new file mode 100644 index 0000000000000000000000000000000000000000..f1093cda325ef6a8213e0f524675fac82de73c10 --- /dev/null +++ b/app/Resources/js/app.ts @@ -0,0 +1,3 @@ +import Dropdown from "./modules/Dropdown"; + +Dropdown(); diff --git a/app/Resources/js/audio-player.ts b/app/Resources/js/audio-player.ts index 0a120376b43693ad804e53a60a5fa8ae26fc80e1..41a17ab8d62b3f56d74257c2df0c4c00ba777361 100644 --- a/app/Resources/js/audio-player.ts +++ b/app/Resources/js/audio-player.ts @@ -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;" > diff --git a/app/Resources/js/modules/play-episode-button.ts b/app/Resources/js/modules/play-episode-button.ts index 7ec0d41705620e31db24c6259919e654115dbff1..d8c51fd49052184c6f67a0d8007e4c65b97ba45a 100644 --- a/app/Resources/js/modules/play-episode-button.ts +++ b/app/Resources/js/modules/play-episode-button.ts @@ -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>`; } } diff --git a/app/Resources/js/podcast.ts b/app/Resources/js/podcast.ts index f3036ec29722eb51b69344ec5eeececbb1f3dd64..c5250eed802e7c67580f172fcf71054d244239cc 100644 --- a/app/Resources/js/podcast.ts +++ b/app/Resources/js/podcast.ts @@ -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(); diff --git a/app/Resources/styles/index.css b/app/Resources/styles/index.css index ec4dd9494bef4587b82f221d814b1099046dcd22..1ce07be46fee2cde8f1e79ab7353cd4298a02f3c 100644 --- a/app/Resources/styles/index.css +++ b/app/Resources/styles/index.css @@ -12,3 +12,5 @@ @import "./radioToggler.css"; @import "./formInputTabs.css"; @import "./stickyHeader.css"; +@import "./readMore.css"; +@import "./seeMore.css"; diff --git a/app/Resources/styles/layout.css b/app/Resources/styles/layout.css index 61b88e4c28fd46cd36a01f33e3d511d8f69f2c25..75875d4e23168f010bb5a563b154e43b717f2e86 100644 --- a/app/Resources/styles/layout.css +++ b/app/Resources/styles/layout.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__sidebar { - @apply col-start-1 col-end-2 row-start-2 row-end-4; - - width: 300px; - max-height: calc(100vh - 2.5rem); - } + & .holy-grail__header { + @apply h-10 col-start-1 col-end-4 row-start-1 row-end-2; + } - & .holy-grail__main { - @apply col-start-1 col-end-3 row-start-2 row-end-4; - } + & .holy-grail__sidebar { + @apply col-start-1 col-end-2 row-start-2 row-end-4; - & .holy-grail__footer { - @apply col-start-1 col-end-3 row-start-3 row-end-4; - } + width: 300px; + max-height: calc(100vh - 2.5rem); + } - @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; + } } } } diff --git a/app/Resources/styles/readMore.css b/app/Resources/styles/readMore.css new file mode 100644 index 0000000000000000000000000000000000000000..d83e0bee0839fdc36603a4c72f96a76888d31c3e --- /dev/null +++ b/app/Resources/styles/readMore.css @@ -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); + } +} diff --git a/app/Resources/styles/seeMore.css b/app/Resources/styles/seeMore.css new file mode 100644 index 0000000000000000000000000000000000000000..2a0d78477d8648430ef47f68ef60488b82fa4720 --- /dev/null +++ b/app/Resources/styles/seeMore.css @@ -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; + } +} diff --git a/app/Views/Components/Button.php b/app/Views/Components/Button.php index dc2080fccce036d9b04fb84e29c81ae69658c37d..c7eb5107198a75bebee9e90b15cc097114c14706 100644 --- a/app/Views/Components/Button.php +++ b/app/Views/Components/Button.php @@ -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', ]; diff --git a/app/Views/Components/DropdownMenu.php b/app/Views/Components/DropdownMenu.php index d806220ec17f8bcef29fad9f32eb9a353e692313..62a0997c49b49151b9c4a0258324319b1f2d1f36 100644 --- a/app/Views/Components/DropdownMenu.php +++ b/app/Views/Components/DropdownMenu.php @@ -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; diff --git a/app/Views/Components/Forms/MarkdownEditor.php b/app/Views/Components/Forms/MarkdownEditor.php index a42bc041f21009962f1aa18b582d7521ed63b573..5307742d2e8bcdd2d65a59f92677219e8da0f5ed 100644 --- a/app/Views/Components/Forms/MarkdownEditor.php +++ b/app/Views/Components/Forms/MarkdownEditor.php @@ -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'), diff --git a/app/Views/Components/ReadMore.php b/app/Views/Components/ReadMore.php new file mode 100644 index 0000000000000000000000000000000000000000..bf9685e89681b5d1534c543633df8c854302aff9 --- /dev/null +++ b/app/Views/Components/ReadMore.php @@ -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; + } +} diff --git a/app/Views/Components/SeeMore.php b/app/Views/Components/SeeMore.php new file mode 100644 index 0000000000000000000000000000000000000000..22481be711895c391a2c65062bb2ab9855603de9 --- /dev/null +++ b/app/Views/Components/SeeMore.php @@ -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; + } +} diff --git a/modules/Admin/Language/en/AdminNavigation.php b/modules/Admin/Language/en/AdminNavigation.php index 92a5f59b676bb571ea162dcbdf4a5ebbb12e98f2..68cbefb3ebcfd0a033bb214d5ba740ed8809206a 100644 --- a/modules/Admin/Language/en/AdminNavigation.php +++ b/modules/Admin/Language/en/AdminNavigation.php @@ -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', diff --git a/modules/Admin/Language/en/Common.php b/modules/Admin/Language/en/Common.php index f05e5d6a7c749ed2ca2f01cf34aabb63f0beee7f..c5048cb9f38564cdef5e439c71cdbf66f544d5c6 100644 --- a/modules/Admin/Language/en/Common.php +++ b/modules/Admin/Language/en/Common.php @@ -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', diff --git a/modules/Admin/Language/en/Episode.php b/modules/Admin/Language/en/Episode.php index 1761148314024a84239eca8ba7a63320034c2e29..990297f8b9171e3a3b4591df72bb3ff3b0a50e44 100644 --- a/modules/Admin/Language/en/Episode.php +++ b/modules/Admin/Language/en/Episode.php @@ -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} diff --git a/modules/Admin/Language/en/Podcast.php b/modules/Admin/Language/en/Podcast.php index 2a107c52d1c037892edc7f2d0098a25f8b46cc59..d2aaa10126b40522c520474f5181d2c2fb2bc0d0 100644 --- a/modules/Admin/Language/en/Podcast.php +++ b/modules/Admin/Language/en/Podcast.php @@ -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', diff --git a/modules/Admin/Language/fr/Common.php b/modules/Admin/Language/fr/Common.php index 2ab2171c708e377953d98f7ab6ac0bb284bfb98c..e02f1b02f0764faad08d7a573ffc218c7861df8c 100644 --- a/modules/Admin/Language/fr/Common.php +++ b/modules/Admin/Language/fr/Common.php @@ -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', diff --git a/modules/Admin/Language/fr/Episode.php b/modules/Admin/Language/fr/Episode.php index bafa62319b8e6bfb1f0d7578f050630cbf2c98d8..857b6754ffaee2519c70f402cf04ad029acc9f02 100644 --- a/modules/Admin/Language/fr/Episode.php +++ b/modules/Admin/Language/fr/Episode.php @@ -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} diff --git a/modules/Admin/Language/fr/Podcast.php b/modules/Admin/Language/fr/Podcast.php index a4919c6d14601883c65eb07df0539bfa085b2aa3..0d332120f9e7ec2a15c58f4fcb911d16c250459a 100644 --- a/modules/Admin/Language/fr/Podcast.php +++ b/modules/Admin/Language/fr/Podcast.php @@ -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', diff --git a/modules/Fediverse/Entities/Post.php b/modules/Fediverse/Entities/Post.php index c6639646078cbb8ca59b5709f7cc4549db18d02d..6177aa371bc85037aac1d3fa2ba0b9cac746fc6e 100644 --- a/modules/Fediverse/Entities/Post.php +++ b/modules/Fediverse/Entities/Post.php @@ -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[] */ diff --git a/package-lock.json b/package-lock.json index 76d0a2a41d219daaeb859d0590fb37723d79d185..90940192987b76e186da9379a2616d6e4b125b3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index e9ba0a513a0c57fbf0746d6add3776a76f4d7053..9af9cb361fd711688906827b9bf658ce4f770845 100644 --- a/package.json +++ b/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", diff --git a/tailwind.config.js b/tailwind.config.js index ea55a83cccf00bd5c5ad5fe78510f622ff63a6a0..dcb69af660323e669c1552ce37c8ac2993e97a7f 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -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"), ], }; diff --git a/themes/cp_admin/_layout.php b/themes/cp_admin/_layout.php index 5269b9e9c067143a6442b0dd8fab91736184767f..c2f212247d8264a12d55bfa1bf82f844fd85b74c 100644 --- a/themes/cp_admin/_layout.php +++ b/themes/cp_admin/_layout.php @@ -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> diff --git a/themes/cp_admin/_partials/_nav_header.php b/themes/cp_admin/_partials/_nav_header.php new file mode 100644 index 0000000000000000000000000000000000000000..1fcac6ae1fd8b734eb0f26556baf9c5d97f68e96 --- /dev/null +++ b/themes/cp_admin/_partials/_nav_header.php @@ -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> \ No newline at end of file diff --git a/themes/cp_admin/episode/edit.php b/themes/cp_admin/episode/edit.php index e82e2aee2c67c1556d23ed6b66b07d628a0990aa..88022107f9a42de3a4093b939bbefa5a98a0b359 100644 --- a/themes/cp_admin/episode/edit.php +++ b/themes/cp_admin/episode/edit.php @@ -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> diff --git a/themes/cp_admin/page/edit.php b/themes/cp_admin/page/edit.php index a8957e20b961fa3c088d3a630f8e70ee58c8d1d6..fc9a8254b946c9f9971fd89b0468d76ea33cb7ec 100644 --- a/themes/cp_admin/page/edit.php +++ b/themes/cp_admin/page/edit.php @@ -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" /> diff --git a/themes/cp_admin/podcast/edit.php b/themes/cp_admin/podcast/edit.php index 8809d9b5d836be026ad923521a1971292cd8d511..71245af11b96627e867273a78756c83f2e79d4b8 100644 --- a/themes/cp_admin/podcast/edit.php +++ b/themes/cp_admin/podcast/edit.php @@ -44,7 +44,7 @@ as="MarkdownEditor" name="description" label="<?= lang('Podcast.form.description') ?>" - value="<?= $podcast->title ?>" + value="<?= htmlspecialchars($podcast->description) ?>" required="true" /> <fieldset> diff --git a/themes/cp_admin/podcast/import.php b/themes/cp_admin/podcast/import.php index 3b96be3e64e4cc42c6f04d9af39b2fba4186e68e..d01838c35cde0b4cc138c3098717bee82b13b86e 100644 --- a/themes/cp_admin/podcast/import.php +++ b/themes/cp_admin/podcast/import.php @@ -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> diff --git a/themes/cp_app/_admin_navbar.php b/themes/cp_app/_admin_navbar.php index aedbab7bb1cb9966deea970125b01bef32af62ae..08a928defb901800d31f35a0ca0d8314a55191ea 100644 --- a/themes/cp_app/_admin_navbar.php +++ b/themes/cp_app/_admin_navbar.php @@ -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> \ No newline at end of file +<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> \ No newline at end of file diff --git a/themes/cp_app/_layout.php b/themes/cp_app/_layout.php index bc8377c60caec9f131607b210ac82f1ec0cc0376..ea8850086325818a47e089edeb32caf70e282d87 100644 --- a/themes/cp_app/_layout.php +++ b/themes/cp_app/_layout.php @@ -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> diff --git a/themes/cp_app/_persons_modal.php b/themes/cp_app/_persons_modal.php new file mode 100644 index 0000000000000000000000000000000000000000..973ddaba49d7346cadadaf6f767d829e80c7b863 --- /dev/null +++ b/themes/cp_app/_persons_modal.php @@ -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> \ No newline at end of file diff --git a/themes/cp_app/episode/_layout.php b/themes/cp_app/episode/_layout.php new file mode 100644 index 0000000000000000000000000000000000000000..fd6a08e36dba2f2769450f25480dbe584a5304b5 --- /dev/null +++ b/themes/cp_app/episode/_layout.php @@ -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> diff --git a/themes/cp_app/episode/_partials/card.php b/themes/cp_app/episode/_partials/card.php new file mode 100644 index 0000000000000000000000000000000000000000..90f26cfc48143bacb3a63e48117dd58602c23916 --- /dev/null +++ b/themes/cp_app/episode/_partials/card.php @@ -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> diff --git a/themes/cp_app/podcast/_partials/comment.php b/themes/cp_app/episode/_partials/comment.php similarity index 77% rename from themes/cp_app/podcast/_partials/comment.php rename to themes/cp_app/episode/_partials/comment.php index 8109c99787a328863e6e90f1dd81efb9acbad26a..a7c0e878f6db1297fc20f1fc932f413aeb27194d 100644 --- a/themes/cp_app/podcast/_partials/comment.php +++ b/themes/cp_app/episode/_partials/comment.php @@ -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> diff --git a/themes/cp_app/episode/_partials/comment_actions.php b/themes/cp_app/episode/_partials/comment_actions.php new file mode 100644 index 0000000000000000000000000000000000000000..245ced8509ef1cc8b74be1b702df6264d4ecd87d --- /dev/null +++ b/themes/cp_app/episode/_partials/comment_actions.php @@ -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> diff --git a/themes/cp_app/episode/_partials/comment_actions_from_post.php b/themes/cp_app/episode/_partials/comment_actions_from_post.php new file mode 100644 index 0000000000000000000000000000000000000000..fb70838f5183708aea586c2707864d27363a02cb --- /dev/null +++ b/themes/cp_app/episode/_partials/comment_actions_from_post.php @@ -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> diff --git a/themes/cp_app/podcast/_partials/comment_card_authenticated.php b/themes/cp_app/episode/_partials/comment_card.php similarity index 58% rename from themes/cp_app/podcast/_partials/comment_card_authenticated.php rename to themes/cp_app/episode/_partials/comment_card.php index 996d2a59da68168e8eeaf97e0fa2ec81fa68c101..e68759e283512a31af455df66ca76a2c7b4728e2 100644 --- a/themes/cp_app/podcast/_partials/comment_card_authenticated.php +++ b/themes/cp_app/episode/_partials/comment_card.php @@ -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> diff --git a/themes/cp_app/podcast/_partials/comment_reply.php b/themes/cp_app/episode/_partials/comment_reply.php similarity index 93% rename from themes/cp_app/podcast/_partials/comment_reply.php rename to themes/cp_app/episode/_partials/comment_reply.php index 3b8f1b037197bc6f0b4532fc19fdc903235e4ae9..8f6d464bfd12bd0b00d8ca1f50a65eefefd9aa4f 100644 --- a/themes/cp_app/podcast/_partials/comment_reply.php +++ b/themes/cp_app/episode/_partials/comment_reply.php @@ -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> diff --git a/themes/cp_app/episode/_partials/comment_reply_actions.php b/themes/cp_app/episode/_partials/comment_reply_actions.php new file mode 100644 index 0000000000000000000000000000000000000000..126167a254dd03b7b38ea82ed0118a99b419ea1a --- /dev/null +++ b/themes/cp_app/episode/_partials/comment_reply_actions.php @@ -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> diff --git a/themes/cp_app/episode/_partials/comment_with_replies.php b/themes/cp_app/episode/_partials/comment_with_replies.php new file mode 100644 index 0000000000000000000000000000000000000000..6c7aed513944cda5944a4d5130183297d8d6b4f0 --- /dev/null +++ b/themes/cp_app/episode/_partials/comment_with_replies.php @@ -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> diff --git a/themes/cp_app/episode/_partials/navigation.php b/themes/cp_app/episode/_partials/navigation.php new file mode 100644 index 0000000000000000000000000000000000000000..e39db5d95570a598ddba385520384146d9779728 --- /dev/null +++ b/themes/cp_app/episode/_partials/navigation.php @@ -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> \ No newline at end of file diff --git a/themes/cp_app/episode/_partials/preview_card.php b/themes/cp_app/episode/_partials/preview_card.php new file mode 100644 index 0000000000000000000000000000000000000000..a9dfe3b9cff08cfa84f861aa88d42cba53cbe202 --- /dev/null +++ b/themes/cp_app/episode/_partials/preview_card.php @@ -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> \ No newline at end of file diff --git a/themes/cp_app/episode/activity.php b/themes/cp_app/episode/activity.php new file mode 100644 index 0000000000000000000000000000000000000000..94c9181894c933860c62f35bfdff63fe7032fe3b --- /dev/null +++ b/themes/cp_app/episode/activity.php @@ -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() ?> diff --git a/themes/cp_app/podcast/comment.php b/themes/cp_app/episode/comment.php similarity index 92% rename from themes/cp_app/podcast/comment.php rename to themes/cp_app/episode/comment.php index b7e66a6589ba590c2a1c3156d3c3aab808b9bccd..87e31e34d6f39634a5e67356f323b9585ddfa072 100644 --- a/themes/cp_app/podcast/comment.php +++ b/themes/cp_app/episode/comment.php @@ -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> diff --git a/themes/cp_app/episode/comments.php b/themes/cp_app/episode/comments.php new file mode 100644 index 0000000000000000000000000000000000000000..6b0e7a317663c06d0a8fbf400e71f05385f617cd --- /dev/null +++ b/themes/cp_app/episode/comments.php @@ -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() +?> diff --git a/themes/cp_app/home.php b/themes/cp_app/home.php index e903879469ef173bb7b379946de5c8288b370998..61c03e3f1a3504918ba55861dfb87850323ae33c 100644 --- a/themes/cp_app/home.php +++ b/themes/cp_app/home.php @@ -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> diff --git a/themes/cp_app/map.php b/themes/cp_app/map.php index b82e424b4f9c7355a20eaea1deef7a40e5a05edc..dc5ec1cbeaf873151189174e10ba2086e438881d 100644 --- a/themes/cp_app/map.php +++ b/themes/cp_app/map.php @@ -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> diff --git a/themes/cp_app/page.php b/themes/cp_app/page.php index 49cea85027a19d95859b3f1bf66c3342733793ef..1cef93164c7ae55434c06a7cff721b9f4f81f5dd 100644 --- a/themes/cp_app/page.php +++ b/themes/cp_app/page.php @@ -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> diff --git a/themes/cp_app/podcast/_layout copy.php b/themes/cp_app/podcast/_layout copy.php deleted file mode 100644 index a37e77b4ac315fd2f0047a819f55c1a756d200af..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_layout copy.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_layout.php b/themes/cp_app/podcast/_layout.php index 7a632a054d6ae90b35c19709a0142cc62e220227..1629b5137868178d8e0c9630517fb775c6929aa8 100644 --- a/themes/cp_app/podcast/_layout.php +++ b/themes/cp_app/podcast/_layout.php @@ -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> diff --git a/themes/cp_app/podcast/_layout_authenticated.php b/themes/cp_app/podcast/_layout_authenticated.php deleted file mode 100644 index eef37085a7e471e6d73122e14652299f25242a8d..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_layout_authenticated.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_navigation.php b/themes/cp_app/podcast/_navigation.php deleted file mode 100644 index 66ce97f9688d0f592beb610bf8df6d97cba03556..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_navigation.php +++ /dev/null @@ -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> \ No newline at end of file diff --git a/themes/cp_app/podcast/_partials/comment_actions.php b/themes/cp_app/podcast/_partials/comment_actions.php deleted file mode 100644 index 93d9eb4a521dc42a4db636d480942e674f5408e0..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/comment_actions.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/comment_actions_authenticated.php b/themes/cp_app/podcast/_partials/comment_actions_authenticated.php deleted file mode 100644 index d4812080ee2f92a72ed70c09f6389a2a9d6f168a..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/comment_actions_authenticated.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/comment_actions_from_post.php b/themes/cp_app/podcast/_partials/comment_actions_from_post.php deleted file mode 100644 index 5026e64e9ae07d7a202dcbaf4d02cdbd78f0491a..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/comment_actions_from_post.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/comment_actions_from_post_authenticated.php b/themes/cp_app/podcast/_partials/comment_actions_from_post_authenticated.php deleted file mode 100644 index a5783ef4577a2a0998cbebe573bb7c6b32a1822e..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/comment_actions_from_post_authenticated.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/comment_authenticated.php b/themes/cp_app/podcast/_partials/comment_authenticated.php deleted file mode 100644 index 0958e5465e565999262f4c025d8c7b3b911110f3..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/comment_authenticated.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/comment_card.php b/themes/cp_app/podcast/_partials/comment_card.php deleted file mode 100644 index c587234b8545f66a87ecd1b3517b9129365ee5fc..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/comment_card.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/comment_reply_actions.php b/themes/cp_app/podcast/_partials/comment_reply_actions.php deleted file mode 100644 index 80213166d987a66a846b29e57ce130d29a02daa9..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/comment_reply_actions.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/comment_reply_actions_authenticated.php b/themes/cp_app/podcast/_partials/comment_reply_actions_authenticated.php deleted file mode 100644 index b11b9c01fd4695a48b5e41193e6584639b24de3d..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/comment_reply_actions_authenticated.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/comment_reply_authenticated.php b/themes/cp_app/podcast/_partials/comment_reply_authenticated.php deleted file mode 100644 index 9602808831cdcc2cc5e3c30b7fbfcbfeaedf6b15..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/comment_reply_authenticated.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/comment_with_replies.php b/themes/cp_app/podcast/_partials/comment_with_replies.php deleted file mode 100644 index 305b988fb171cb6a6fa5410d5a9d6250683003e2..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/comment_with_replies.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/comment_with_replies_authenticated.php b/themes/cp_app/podcast/_partials/comment_with_replies_authenticated.php deleted file mode 100644 index dfd69dcc93f8674202fa38518eb448a9b5b15771..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/comment_with_replies_authenticated.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/episode_card.php b/themes/cp_app/podcast/_partials/episode_card.php deleted file mode 100644 index a77d2b15d0aeafe7b733a06ab8a306d0a17aff87..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/episode_card.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/episode_preview_card.php b/themes/cp_app/podcast/_partials/episode_preview_card.php deleted file mode 100644 index 6ad4b9092ba5ba1fc4e497757972e952c1cb1aae..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/episode_preview_card.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/funding_links_modal.php b/themes/cp_app/podcast/_partials/funding_links_modal.php new file mode 100644 index 0000000000000000000000000000000000000000..371b191bb7f345e63d9e723d94eefd9c309f1ff9 --- /dev/null +++ b/themes/cp_app/podcast/_partials/funding_links_modal.php @@ -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> \ No newline at end of file diff --git a/themes/cp_app/podcast/_partials/header.php b/themes/cp_app/podcast/_partials/header.php deleted file mode 100644 index f5d57ea96bf8b464c0b2a954f755dc987a94213c..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/header.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/navigation.php b/themes/cp_app/podcast/_partials/navigation.php new file mode 100644 index 0000000000000000000000000000000000000000..21de98414039ee5405032e17114971a9f4771a96 --- /dev/null +++ b/themes/cp_app/podcast/_partials/navigation.php @@ -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> \ No newline at end of file diff --git a/themes/cp_app/podcast/_partials/post.php b/themes/cp_app/podcast/_partials/post.php deleted file mode 100644 index 3d40f4604bc06682677386b65a81b982c9f20717..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/post.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/post_actions.php b/themes/cp_app/podcast/_partials/post_actions.php deleted file mode 100644 index bf29cccca33b8cf5f9d162c5c2e9f5b6b9595b3a..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/post_actions.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/post_actions_authenticated.php b/themes/cp_app/podcast/_partials/post_actions_authenticated.php deleted file mode 100644 index a50c87db2ecaed67c4737a1b1127c6631f38f590..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/post_actions_authenticated.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/post_with_replies.php b/themes/cp_app/podcast/_partials/post_with_replies.php deleted file mode 100644 index ddf3489fe557bea1a1db00133a7560487627f172..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/post_with_replies.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/post_with_replies_authenticated.php b/themes/cp_app/podcast/_partials/post_with_replies_authenticated.php deleted file mode 100644 index 9dbd498a434bec7dfdeeff4d87977d4b077993e9..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/post_with_replies_authenticated.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/reblog_authenticated.php b/themes/cp_app/podcast/_partials/reblog_authenticated.php deleted file mode 100644 index cc8a4b4132cf33cb4c049d7e114630484ddec6a0..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/reblog_authenticated.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/reply_actions.php b/themes/cp_app/podcast/_partials/reply_actions.php deleted file mode 100644 index a11b9da360c948af790c46d5a0978bdbdabc7f0d..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/reply_actions.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/reply_actions_authenticated.php b/themes/cp_app/podcast/_partials/reply_actions_authenticated.php deleted file mode 100644 index ea54f20d232a217490f6c5dfd1fd445d883d0ba6..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/reply_actions_authenticated.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/reply_authenticated.php b/themes/cp_app/podcast/_partials/reply_authenticated.php deleted file mode 100644 index 582c7594812e021b0b570d0dd8affc0b886a10ae..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/_partials/reply_authenticated.php +++ /dev/null @@ -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> diff --git a/themes/cp_app/podcast/_partials/sidebar.php b/themes/cp_app/podcast/_partials/sidebar.php index 6e0853f59e841b39f9e6efec7bcd6cc636e6462a..2af44f6b483ec5815e22ebb8a3ff0db89f4bc385 100644 --- a/themes/cp_app/podcast/_partials/sidebar.php +++ b/themes/cp_app/podcast/_partials/sidebar.php @@ -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, - ], - ) ?> +<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 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): ?> - <?= anchor( + <?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> \ No newline at end of file diff --git a/themes/cp_app/podcast/about.php b/themes/cp_app/podcast/about.php index 436beeae4b46a18d7fb871808ca38643c0ae5f75..a616a64f5afbcf80092ba385692da3a7d8d09ecd 100644 --- a/themes/cp_app/podcast/about.php +++ b/themes/cp_app/podcast/about.php @@ -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() ?> diff --git a/themes/cp_app/podcast/activity.php b/themes/cp_app/podcast/activity.php index 1a1b5176fad0b225135447f9f04e076db071e300..1fa7a529c2ce7c7bd02c675718f8cc04fb0fd80c 100644 --- a/themes/cp_app/podcast/activity.php +++ b/themes/cp_app/podcast/activity.php @@ -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() ?> diff --git a/themes/cp_app/podcast/activity_authenticated.php b/themes/cp_app/podcast/activity_authenticated.php deleted file mode 100644 index 8e582c7020d4462f68fc9cbe0fd1a9b0a5853d0c..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/activity_authenticated.php +++ /dev/null @@ -1,73 +0,0 @@ -<?= $this->extend('podcast/_layout_authenticated') ?> - -<?= $this->section('meta-tags') ?> -<link type="application/rss+xml" rel="alternate" title="<?= $podcast->title ?>" href="<?= $podcast->feed_url ?>"/> - -<title><?= $podcast->title ?></title> -<meta name="description" content="<?= htmlspecialchars( - $podcast->description, -) ?>" /> -<link rel="shortcut icon" type="image/png" href="/favicon.ico" /> -<link rel="canonical" href="<?= current_url() ?>" /> -<meta property="og:title" content="<?= $podcast->title ?>" /> -<meta property="og:description" content="<?= $podcast->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="<?= $podcast->image->large_url ?>" /> -<meta property="og:image:width" content="<?= config('Images') - ->largeSize ?>" /> -<meta property="og:image:height" content="<?= config('Images') - ->largeSize ?>" /> -<meta name="twitter:card" content="summary_large_image" /> - -<?= service('vite') - ->asset('styles/index.css', 'css') ?> -<?= $this->endSection() ?> - -<?= $this->section('content') ?> - -<?= $this->include('podcast/_navigation') ?> - -<section class="max-w-2xl px-6 py-8 mx-auto"> -<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"><?= lang('Post.form.submit') ?></Button> - </div> -</form> -<hr class="my-4 border-2 border-pine-100"> - -<div class="space-y-8"> -<?php foreach ($posts as $post): ?> - <?php if ($post->reblog_of_id !== null): ?> - <?= view('podcast/_partials/reblog_authenticated', [ - 'post' => $post->reblog_of_post, - 'podcast' => $podcast, -]) ?> - <?php else: ?> - <?= view('podcast/_partials/post_authenticated', [ - 'post' => $post, - 'podcast' => $podcast, -]) ?> - <?php endif; ?> -<?php endforeach; ?> -</div> -</section> - -<?= $this->endSection() ?> diff --git a/themes/cp_app/podcast/comment_authenticated.php b/themes/cp_app/podcast/comment_authenticated.php deleted file mode 100644 index ea145a420b7bb152d1049fdd7a888c5fde154f86..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/comment_authenticated.php +++ /dev/null @@ -1,41 +0,0 @@ -<?= $this->extend('podcast/_layout_authenticated') ?> - -<?= $this->section('meta-tags') ?> - <title><?= lang('Comment.title', [ - 'actorDisplayName' => $comment->actor->display_name, - 'episodeTitle' => $episode->title, -]) ?></title> - <meta name="description" content="<?= $comment->message ?>"/> - <meta property="og:title" content="<?= lang('Comment.title', [ - 'actorDisplayName' => $comment->actor->display_name, - 'episodeTitle' => $episode->title, - ]) ?>"/> - <meta property="og:locale" content="<?= service( - 'request', - )->getLocale() ?>" /> - <meta property="og:site_name" content="<?= $comment->actor->display_name ?>" /> - <meta property="og:url" content="<?= current_url() ?>" /> - <meta property="og:image" content="<?= $comment->actor->avatar_image_url ?>" /> - <meta property="og:description" content="<?= $comment->message ?>" /> -<?= $this->endSection() ?> - -<?= $this->section('content') ?> -<div class="max-w-2xl px-6 mx-auto"> - <nav class="py-3"> - <a href="<?= route_to('episode', $podcast->handle, $episode->slug) ?>" - class="inline-flex items-center px-4 py-2 text-sm"><?= icon( - 'arrow-left', - 'mr-2 text-lg', - ) . - lang('Comment.back_to_episode', [ - 'episodeTitle' => $episode->title, - ]) ?></a> - </nav> - <div class="pb-12"> - <?= $this->include( - 'podcast/_partials/comment_with_replies_authenticated', - ) ?> - </div> -</div> - -<?= $this->endSection() ?> diff --git a/themes/cp_app/podcast/episode.php b/themes/cp_app/podcast/episode.php deleted file mode 100644 index dcccf80c17725ec7593d55ee3497c288eab019d7..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/episode.php +++ /dev/null @@ -1,113 +0,0 @@ -<?= $this->extend('podcast/_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="max-w-2xl mx-auto"> - <a href="<?= route_to( - 'podcast-episodes', - $podcast->handle, - ) ?>" class="inline-flex items-center px-4 py-2 mb-2 text-sm"><?= icon( - 'arrow-left', - 'mr-2 text-lg', - ) . lang('Episode.back_to_episodes', [ - 'podcast' => $podcast->title, - ]) ?></a> - <header class="flex flex-col px-6 mb-4 rounded-b-xl"> - <div class="flex flex-wrap items-start"> - <img src="<?= $episode->image - ->medium_url ?>" alt="<?= $episode->title ?>" class="mb-4 mr-6 rounded-xl w-52" /> - <div class="flex flex-col items-start flex-1 mb-4" style="min-width: 14rem"> - <h1 class="text-xl font-bold leading-none font-display line-clamp-2"><?= $episode->title ?></h1> - <?= episode_numbering( - $episode->number, - $episode->season_number, - 'text-gray-700', - ) ?> - <div class="mb-4 text-xs"> - <?= relative_time($episode->published_at) ?> - <span class="mx-1">•</span> - <time datetime="PT<?= $episode->audio_file_duration ?>S"> - <?= format_duration($episode->audio_file_duration) ?> - </time> - </div> - <?= location_link($episode->location, 'text-sm mb-4') ?> - <?= person_list($episode->persons) ?> - <?= play_episode_button($episode->id, $episode->image->thumbnail_url, $episode->title, $podcast->title, $episode->audio_file_web_url, $episode->audio_file_mimetype) ?> - </div> - </div> - </header> - - <div class="tabset"> - <input type="radio" name="tabset" id="comments" aria-controls="comments" checked="checked" /> - <label for="comments"><?= lang('Episode.comments') . ' (' . $episode->comments_count . ')' ?></label> - - <input type="radio" name="tabset" id="activity" aria-controls="activity" /> - <label for="activity"><?= lang('Episode.activity') . ' (' . $episode->posts_count . ')' ?></label> - - <input type="radio" name="tabset" id="description" aria-controls="description" /> - <label for="description"><?= lang('Episode.description') ?></label> - - <div class="tab-panels"> - <section id="comments" class="space-y-6 tab-panel"> - <?php foreach ($episode->comments as $comment): ?> - <?= view('podcast/_partials/comment', [ - 'comment' => $comment, - ]) ?> - <?php endforeach; ?> - </section> - <section id="activity" class="space-y-8 tab-panel"> - <?php foreach ($episode->posts as $post): ?> - <?= view('podcast/_partials/post', [ - 'post' => $post, - 'podcast' => $podcast, - ]) ?> - <?php endforeach; ?> - </section> - <section id="description" class="prose tab-panel"> - <?= $episode->getDescriptionHtml('-+Website+-') ?> - </section> - </div> - </div> -</div> - -<?= $this->endSection() -?> diff --git a/themes/cp_app/podcast/episode_authenticated.php b/themes/cp_app/podcast/episode_authenticated.php deleted file mode 100644 index 0ffc7ed58dba18c0edaa27d11953c6f92d3a1451..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/episode_authenticated.php +++ /dev/null @@ -1,150 +0,0 @@ -<?= $this->extend('podcast/_layout_authenticated') ?> - -<?= $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="max-w-2xl mx-auto"> - <a href="<?= route_to( - 'podcast-episodes', - $podcast->handle, - ) ?>" class="inline-flex items-center px-4 py-2 mb-2 text-sm"><?= icon( - 'arrow-left', - 'mr-2 mb- text-lg', - ) . lang('Episode.back_to_episodes', [ - 'podcast' => $podcast->title, - ]) ?></a> - <header class="flex flex-col px-6 mb-4 rounded-b-xl"> - <div class="flex flex-wrap items-start"> - <img src="<?= $episode->image - ->medium_url ?>" alt="<?= $episode->title ?>" class="mb-4 mr-6 rounded-xl w-52" /> - <div class="flex flex-col items-start flex-1 mb-4" style="min-width: 14rem"> - <h1 class="text-xl font-bold leading-none font-display line-clamp-2"><?= $episode->title ?></h1> - <?= episode_numbering( - $episode->number, - $episode->season_number, - 'text-gray-700', - ) ?> - <div class="mb-4 text-xs"> - <?= relative_time($episode->published_at) ?> - <span class="mx-1">•</span> - <time datetime="PT<?= $episode->audio_file_duration ?>S"> - <?= format_duration($episode->audio_file_duration) ?> - </time> - </div> - <?= location_link($episode->location, 'text-sm mb-4') ?> - <?= person_list($episode->persons) ?> - <?= play_episode_button($episode->id, $episode->image->thumbnail_url, $episode->title, $podcast->title, $episode->audio_file_web_url, $episode->audio_file_mimetype) ?> - </div> - </div> - </header> - - <div class="tabset"> - <input type="radio" name="tabset" id="comments" aria-controls="comments" checked="checked" /> - <label for="comments"><?= lang('Episode.comments') . ' (' . $episode->comments_count . ')' ?></label> - - <input type="radio" name="tabset" id="activity" aria-controls="activity" /> - <label for="activity"><?= lang('Episode.activity') . ' (' . $episode->posts_count . ')' ?></label> - - <input type="radio" name="tabset" id="description" aria-controls="description" /> - <label for="description"><?= lang('Episode.description') ?></label> - - <div class="tab-panels"> - <section id="comments" class="space-y-6 tab-panel"> - <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-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('Comment.form.episode_message_placeholder') ?>" - rows="2" /> - <Button class="self-end" variant="primary" size="small" type="submit"><?= lang('Comment.form.submit') ?></Button> - </div> - </form> - <?php foreach ($episode->comments as $comment): ?> - <?= view('podcast/_partials/comment_authenticated', [ - 'comment' => $comment, - 'podcast' => $podcast, - ]) ?> - <?php endforeach; ?> - </section> - <section id="activity" class="space-y-8 tab-panel"> - <form action="<?= route_to('post-attempt-create', $podcast->handle) ?>" 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"> - <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"><?= lang('Post.form.submit') ?></Button> - </div> - </form> - <hr class="my-4 border border-pine-100"> - <?php foreach ($episode->posts as $post): ?> - <?= view('podcast/_partials/post_authenticated', [ - 'post' => $post, - 'podcast' => $podcast, - ]) ?> - <?php endforeach; ?> - </section> - <section id="description" class="prose tab-panel"> - <?= $episode->getDescriptionHtml('-+Website+-') ?> - </section> - </div> - </div> -</div> - -<?= $this->endSection() -?> diff --git a/themes/cp_app/podcast/episodes.php b/themes/cp_app/podcast/episodes.php index ed4a9a523e897edebde1a50f1d3be2360ab76a6a..d4c26f623b20ce8764e487396da52af606981bcc 100644 --- a/themes/cp_app/podcast/episodes.php +++ b/themes/cp_app/podcast/episodes.php @@ -27,10 +27,10 @@ <?= $this->section('content') ?> -<section class="flex flex-col max-w-2xl"> +<section class="flex flex-col max-w-2xl gap-y-4"> <?php if ($episodes): ?> - <div class="flex items-center justify-between mb-4"> - <h1 class="text-xl font-semibold"> + <div class="flex items-center justify-between"> + <h1 class="font-semibold"> <?php if ($activeQuery['type'] === 'year'): ?> <?= lang('Podcast.list_of_episodes_year', [ 'year' => $activeQuery['value'], @@ -44,34 +44,28 @@ <?php endif; ?> </h1> <?php if ($activeQuery): ?> - <button id="episode-lists-dropdown" type="button" class="inline-flex items-center px-2 py-1 text-sm font-semibold outline-none focus:ring" data-dropdown="button" data-dropdown-target="episode-lists-dropdown-menu" aria-label="<?= lang( - 'Common.more', -) ?>" aria-haspopup="true" aria-expanded="false"> - <?= $activeQuery['label'] . - ' (' . - $activeQuery['number_of_episodes'] . - ')' . - icon('caret-down', 'ml-2 text-xl') ?> + <button id="episode-lists-dropdown" type="button" class="inline-flex items-center px-2 py-1 text-sm font-semibold outline-none focus:ring" data-dropdown="button" data-dropdown-target="episode-lists-dropdown-menu" aria-label="<?= lang('Common.more') ?>" aria-haspopup="true" aria-expanded="false"> + <?= $activeQuery['label'] . icon('caret-down', 'ml-2 text-xl') ?> </button> <nav id="episode-lists-dropdown-menu" class="flex flex-col py-2 text-black bg-white border rounded shadow" aria-labelledby="episode-lists-dropdown" data-dropdown="menu" data-dropdown-placement="bottom-end"> <?php foreach ($episodesNav as $link): ?> <?= anchor( - $link['route'], - $link['label'] . ' (' . $link['number_of_episodes'] . ')', - [ - 'class' => - 'px-2 py-1 whitespace-nowrap ' . - ($link['is_active'] - ? 'font-semibold' - : 'text-gray-600 hover:text-gray-900'), - ], - ) ?> + $link['route'], + $link['label'] . ' (' . $link['number_of_episodes'] . ')', + [ + 'class' => + 'px-2 py-1 whitespace-nowrap ' . + ($link['is_active'] + ? 'font-semibold' + : 'text-gray-600 hover:text-gray-900'), + ], +) ?> <?php endforeach; ?> </nav> <?php endif; ?> </div> <?php foreach ($episodes as $episode): ?> - <?= view('podcast/_partials/episode_card', [ + <?= view('episode/_partials/card', [ 'episode' => $episode, 'podcast' => $podcast, ]) ?> diff --git a/themes/cp_app/podcast/episodes_authenticated.php b/themes/cp_app/podcast/episodes_authenticated.php deleted file mode 100644 index 0986f2eb0d372df29fe47e4d776d1a679361fc91..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/episodes_authenticated.php +++ /dev/null @@ -1,63 +0,0 @@ -<?= $this->extend('podcast/_layout_authenticated') ?> - -<?= $this->section('meta-tags') ?> -<link type="application/rss+xml" rel="alternate" title="<?= $podcast->title ?>" href="<?= $podcast->feed_url ?>" /> - -<title><?= $podcast->title ?></title> -<meta name="description" content="<?= htmlspecialchars( - $podcast->description, -) ?>" /> -<link rel="shortcut icon" type="image/png" href="/favicon.ico" /> -<link rel="canonical" href="<?= current_url() ?>" /> -<meta property="og:title" content="<?= $podcast->title ?>" /> -<meta property="og:description" content="<?= $podcast->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="<?= $podcast->image->large_url ?>" /> -<meta property="og:image:width" content="<?= config('Images') - ->largeSize ?>" /> -<meta property="og:image:height" content="<?= config('Images') - ->largeSize ?>" /> -<meta name="twitter:card" content="summary_large_image" /> - -<?= service('vite') - ->asset('styles/index.css', 'css') ?> -<?= $this->endSection() ?> - -<?= $this->section('content') ?> - -<?= $this->include('podcast/_navigation') ?> - -<section class="flex flex-col max-w-2xl px-6 py-8 mx-auto"> - - <?php if ($episodes) : ?> - <h1 class="mb-4 text-xl font-semibold"> - <?php if ($activeQuery['type'] === 'year') : ?> - <?= lang('Podcast.list_of_episodes_year', [ - 'year' => $activeQuery['value'], - 'episodeCount' => count($episodes), -]) ?> - <?php elseif ($activeQuery['type'] === 'season') : ?> - <?= lang('Podcast.list_of_episodes_season', [ - 'seasonNumber' => $activeQuery['value'], - 'episodeCount' => count($episodes), -]) ?> - <?php endif; ?> - </h1> - <?php foreach ($episodes as $episode) : ?> - <?= view('podcast/_partials/episode_card', [ - 'episode' => $episode, - 'podcast' => $podcast, -]) ?> - <?php endforeach; ?> - <?php else : ?> - <h1 class="px-4 mb-2 text-xl text-center"><?= lang( - 'Podcast.no_episode', -) ?></h1> - <p class="italic text-center"><?= lang('Podcast.no_episode_hint') ?></p> - <?php endif; ?> -</section> - -<?= $this->endSection() -?> diff --git a/themes/cp_app/podcast/follow.php b/themes/cp_app/podcast/follow.php index 32682ae9b756bfa2c2d4eac358135674d488a0e0..e98440f71aeca44487e43e84ed80bf2ec0802f2f 100644 --- a/themes/cp_app/podcast/follow.php +++ b/themes/cp_app/podcast/follow.php @@ -49,16 +49,16 @@ <main class="w-full max-w-md px-4 mx-auto"> <form action="<?= route_to('attempt-follow', $actor->username) ?>" method="POST" class="flex flex-col"> - <?= csrf_field() ?> - <?= view('_message_block') ?> + <?= csrf_field() ?> + <?= view('_message_block') ?> - <Forms.Field - name="handle" - label="<?= lang('Fediverse.your_handle') ?>" - hint="<?= lang('Fediverse.your_handle_hint') ?>" - required="true" - /> - <Button variant="primary" type="submit" class="self-end"><?= lang('Fediverse.follow.submit') ?></Button> + <Forms.Field + name="handle" + label="<?= lang('Fediverse.your_handle') ?>" + hint="<?= lang('Fediverse.your_handle_hint') ?>" + required="true" + /> + <Button variant="primary" type="submit" class="self-end"><?= lang('Fediverse.follow.submit') ?></Button> </form> </main> @@ -68,7 +68,7 @@ <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 hover:underline" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a>', ]) ?> </p> </footer> diff --git a/themes/cp_app/podcast/post_authenticated.php b/themes/cp_app/podcast/post_authenticated.php deleted file mode 100644 index f04699351cdfbd492cb481d211aeaf85d0d28e14..0000000000000000000000000000000000000000 --- a/themes/cp_app/podcast/post_authenticated.php +++ /dev/null @@ -1,40 +0,0 @@ -<?= $this->extend('podcast/_layout_authenticated') ?> - -<?= $this->section('meta-tags') ?> - <title><?= lang('Post.title', [ - 'actorDisplayName' => $post->actor->display_name, - ]) ?></title> - <meta name="description" content="<?= $post->message ?>"/> - <meta property="og:title" content="<?= lang('Post.title', [ - 'actorDisplayName' => $post->actor->display_name, - ]) ?>"/> - <meta property="og:locale" content="<?= service( - 'request', - )->getLocale() ?>" /> - <meta property="og:site_name" content="<?= $post->actor->display_name ?>" /> - <meta property="og:url" content="<?= current_url() ?>" /> - <meta property="og:image" content="<?= $post->actor->avatar_image_url ?>" /> - <meta property="og:description" content="<?= $post->message ?>" /> -<?= $this->endSection() ?> - -<?= $this->section('content') ?> -<div class="max-w-2xl px-6 mx-auto"> - <nav class="py-3"> - <a href="<?= route_to('podcast-activity', $podcast->handle) ?>" - class="inline-flex items-center px-4 py-2 text-sm"><?= icon( - 'arrow-left', - 'mr-2 text-lg', - ) . - lang('Post.back_to_actor_posts', [ - 'actor' => $post->actor->display_name, - ]) ?></a> - </nav> - <div class="pb-12"> - <?= $this->include( - 'podcast/_partials/post_with_replies_authenticated', - ) ?> - </div> -</div> - -<?= $this->endSection() -?> diff --git a/themes/cp_app/post/_partials/actions.php b/themes/cp_app/post/_partials/actions.php new file mode 100644 index 0000000000000000000000000000000000000000..2f92da4bcf3436f46a466b28abd6e8d8a9ee195d --- /dev/null +++ b/themes/cp_app/post/_partials/actions.php @@ -0,0 +1,124 @@ +<footer class="flex justify-around px-6 py-3"> + <?php if (can_user_interact()): ?> + <form action="<?= route_to('post-attempt-action', interact_as_actor()->username, $post->id) ?>" method="POST" class="flex justify-around w-full"> + <?= 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> + <?php else: ?> + <?= 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, + ]), + ], + ) ?> + <?php endif; ?> +</footer> diff --git a/themes/cp_app/podcast/_partials/post_authenticated.php b/themes/cp_app/post/_partials/card.php similarity index 63% rename from themes/cp_app/podcast/_partials/post_authenticated.php rename to themes/cp_app/post/_partials/card.php index 9a60f6b36fb547693d7db7054c71cc32f8196721..74e4f4de5a61a109582af52539d466115b6e4ff0 100644 --- a/themes/cp_app/podcast/_partials/post_authenticated.php +++ b/themes/cp_app/post/_partials/card.php @@ -1,7 +1,7 @@ -<article class="relative z-10 w-full bg-white shadow rounded-2xl"> +<article class="relative z-10 w-full bg-white shadow sm: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" /> + ->avatar_image_url ?>" alt="<?= $post->actor->display_name ?>" class="w-10 h-10 mr-2 rounded-full" /> <div class="flex flex-col min-w-0"> <a href="<?= $post->actor ->uri ?>" class="flex items-baseline hover:underline" <?= $post @@ -22,15 +22,20 @@ </a> </div> </header> - <div class="px-6 mb-4 post-content"><?= $post->message_html ?></div> + <?php if (substr_count($post->message, "\n") >= 3 || strlen($post->message) > 250): ?> + <ReadMore id="<?= $index ?>" class="px-6 mb-4 post-content"><?= $post->message_html ?></ReadMore> + <?php else: ?> + <div class="px-6 mb-4 post-content"><?= $post->message_html ?></div> + <?php endif; ?> <?php if ($post->episode_id): ?> - <?= view('podcast/_partials/episode_preview_card', [ + <?= view('episode/_partials/preview_card', [ + 'index' => $index, 'episode' => $post->episode, - ]) ?> - <?php elseif ($post->has_preview_card): ?> - <?= view('podcast/_partials/preview_card', [ +]) ?> + <?php elseif ($post->preview_card_id): ?> + <?= view('post/_partials/preview_card', [ 'preview_card' => $post->preview_card, ]) ?> <?php endif; ?> - <?= $this->include('podcast/_partials/post_actions_authenticated') ?> + <?= $this->include('post/_partials/actions') ?> </article> diff --git a/themes/cp_app/post/_partials/post_with_replies.php b/themes/cp_app/post/_partials/post_with_replies.php new file mode 100644 index 0000000000000000000000000000000000000000..fc3b66a22fd9f3c848e08d8c6386efa294799342 --- /dev/null +++ b/themes/cp_app/post/_partials/post_with_replies.php @@ -0,0 +1,49 @@ +<?= view('post/_partials/card', [ + 'index' => $index, + 'post' => $post, + 'podcast' => $podcast, +]) ?> +<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"> + <?php if (can_user_interact()): ?> + <form action="<?= route_to('post-attempt-action', interact_as_actor()->username, $post->id) ?>" method="POST" class="flex" > + <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" iconRight="send-plane"><?= lang('Post.form.submit_reply') ?></Button> + </div> + </form> + <?php else: ?> + <?= 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, + ], + ) ?> + <?php endif; ?> + </div> + + <?php if ($post->has_replies): ?> + <?php foreach ($post->replies as $reply): ?> + <?= view('post/_partials/reply', [ + 'podcast' => $podcast, + 'reply' => $reply, + ]) ?> + <?php endforeach; ?> + <?php endif; ?> +</div> \ No newline at end of file diff --git a/themes/cp_app/podcast/_partials/preview_card.php b/themes/cp_app/post/_partials/preview_card.php similarity index 100% rename from themes/cp_app/podcast/_partials/preview_card.php rename to themes/cp_app/post/_partials/preview_card.php diff --git a/themes/cp_app/podcast/_partials/reblog.php b/themes/cp_app/post/_partials/reblog.php similarity index 83% rename from themes/cp_app/podcast/_partials/reblog.php rename to themes/cp_app/post/_partials/reblog.php index d5ef3ba136f1218a5a9cbd1dca687452720f747e..71914ded4c14df9eb4cccfac651507f147f2fb6a 100644 --- a/themes/cp_app/podcast/_partials/reblog.php +++ b/themes/cp_app/post/_partials/reblog.php @@ -1,4 +1,4 @@ -<article class="relative z-10 w-full bg-white shadow rounded-2xl"> +<article class="relative z-10 w-full bg-white shadow sm:rounded-2xl"> <p class="inline-flex px-6 pt-4 text-xs text-gray-700"><?= icon( 'repeat', 'text-lg mr-2 text-gray-400', @@ -31,13 +31,13 @@ </header> <div class="px-6 mb-4 post-content"><?= $post->message_html ?></div> <?php if ($post->episode_id): ?> - <?= view('podcast/_partials/episode_preview_card', [ + <?= view('episode/_partials/preview_card', [ 'episode' => $post->episode, ]) ?> - <?php elseif ($post->has_preview_card): ?> - <?= view('podcast/_partials/preview_card', [ + <?php elseif ($post->preview_card_id): ?> + <?= view('post/_partials/preview_card', [ 'preview_card' => $post->preview_card, ]) ?> <?php endif; ?> - <?= $this->include('podcast/_partials/post_actions') ?> + <?= $this->include('post/_partials/actions') ?> </article> diff --git a/themes/cp_app/podcast/_partials/reply.php b/themes/cp_app/post/_partials/reply.php similarity index 77% rename from themes/cp_app/podcast/_partials/reply.php rename to themes/cp_app/post/_partials/reply.php index 912276ad7e8b72bb7b5316b6af2a3e044dcce1a3..e39bc418a8d1b8947859ddc643989aae83d7af49 100644 --- a/themes/cp_app/podcast/_partials/reply.php +++ b/themes/cp_app/post/_partials/reply.php @@ -11,14 +11,14 @@ ->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') ?> + <?= relative_time($reply->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', [ + <?php if ($reply->preview_card_id): ?> + <?= view('post/_partials/preview_card', [ 'preview_card' => $reply->preview_card, ]) ?> <?php endif; ?> - <?= $this->include('podcast/_partials/reply_actions') ?> + <?= $this->include('post/_partials/reply_actions') ?> </div> </article> diff --git a/themes/cp_app/post/_partials/reply_actions.php b/themes/cp_app/post/_partials/reply_actions.php new file mode 100644 index 0000000000000000000000000000000000000000..322f1ed3be8741977935c779270dbe88f76066c0 --- /dev/null +++ b/themes/cp_app/post/_partials/reply_actions.php @@ -0,0 +1,124 @@ +<footer> + <?php if (can_user_interact()): ?> + <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> + <?php else: ?> + <?= 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, + ]), + ], + ) ?> + <?php endif; ?> +</footer> diff --git a/themes/cp_app/podcast/post.php b/themes/cp_app/post/post.php similarity index 65% rename from themes/cp_app/podcast/post.php rename to themes/cp_app/post/post.php index 3990e38d8c57a93612df602af24ee2cef642e4a1..3cc1299f10393a237e7b7c91ba038a9748f8a5ff 100644 --- a/themes/cp_app/podcast/post.php +++ b/themes/cp_app/post/post.php @@ -18,21 +18,21 @@ <?= $this->endSection() ?> <?= $this->section('content') ?> -<div class="max-w-2xl px-6 mx-auto"> - <nav class="py-3"> - <a href="<?= route_to('podcast-activity', $podcast->handle) ?>" - class="inline-flex items-center px-4 py-2 text-sm"><?= icon( +<nav class="py-2"> + <a href="<?= route_to('podcast-activity', $podcast->handle) ?>" + class="inline-flex items-center px-4 py-2 text-sm"><?= icon( 'arrow-left', 'mr-2 text-lg', ) . - lang('Post.back_to_actor_posts', [ - 'actor' => $post->actor->display_name, - ]) ?></a> - </nav> - <div class="pb-12"> - <?= $this->include('podcast/_partials/post_with_replies') ?> - </div> + lang('Post.back_to_actor_posts', [ + 'actor' => $post->actor->display_name, + ]) ?></a> +</nav> +<div class="pb-12"> + <?= view('post/_partials/post_with_replies', [ + 'index' => 1, + 'post' => $post, + 'podcast' => $podcast, + ]) ?> </div> - -<?= $this->endSection() -?> +<?= $this->endSection() ?> diff --git a/themes/cp_app/podcast/post_remote_action.php b/themes/cp_app/post/remote_action.php similarity index 97% rename from themes/cp_app/podcast/post_remote_action.php rename to themes/cp_app/post/remote_action.php index 6e2205abe65d10ece9ee6f041f8bc00809ff9fe0..36c7ffd3614d28dec1fcb26ecb3323d5bfca0fd1 100644 --- a/themes/cp_app/podcast/post_remote_action.php +++ b/themes/cp_app/post/remote_action.php @@ -38,7 +38,7 @@ ) ?></h1> </header> <main class="flex-1 max-w-xl px-4 pb-8 mx-auto -mt-24"> - <?= $this->include('podcast/_partials/post') ?> + <?= $this->include('post/_partials/card') ?> <form action="<?= route_to('post-attempt-remote-action', $post->id, $action) ?>" method="POST" class="flex flex-col mt-8"> <?= csrf_field() ?> diff --git a/themes/cp_auth/_layout.php b/themes/cp_auth/_layout.php index 30fca7a6e16a023aa6694c814b1ea5eeff4ea26d..6df0641baae40cd6819beea9653b6ecae932ef98 100644 --- a/themes/cp_auth/_layout.php +++ b/themes/cp_auth/_layout.php @@ -31,7 +31,7 @@ <?= $this->renderSection('footer') ?> <small class="py-4 text-center border-t-2 border-pine-100"><?= 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" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a>', ]) ?></small> </footer> </body> diff --git a/themes/cp_install/_layout.php b/themes/cp_install/_layout.php index eccc912fbf58d28d6eca94ea6a46ec6248580413..dd384d75e9462a33b86168be53cdfa7b8e2755e9 100644 --- a/themes/cp_install/_layout.php +++ b/themes/cp_install/_layout.php @@ -26,7 +26,7 @@ <footer class="container px-2 py-4 mx-auto text-sm text-right border-t"> <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> diff --git a/vite.config.ts b/vite.config.ts index f8b3ed543f8cf044178fc82c500f573f088ba220..617f6fa34469bc7cdd69f247a81cb2f8451fcaef 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -14,6 +14,7 @@ export default defineConfig({ input: { "podcast.ts": "app/Resources/js/podcast.ts", "install.ts": "app/Resources/js/install.ts", + "app.ts": "app/Resources/js/app.ts", "admin.ts": "app/Resources/js/admin.ts", "charts.ts": "app/Resources/js/charts.ts", "map.ts": "app/Resources/js/map.ts",