From 902f959b30a10839684f093eb86edebc5d826a0b Mon Sep 17 00:00:00 2001 From: Yassine Doghri <yassine@doghri.fr> Date: Fri, 12 Nov 2021 16:31:35 +0000 Subject: [PATCH] feat: add schema.org json-ld objects to podcasts, episodes, posts and comments pages - refactor meta-tags by generating them in the controller and injecting them into the views - use `melbahja/seo` library to build opengraph and twitter meta-tags + schema.org objects --- app/Config/App.php | 2 + app/Config/Embed.php | 19 ++ app/Config/Routes.php | 4 +- app/Controllers/ActorController.php | 4 +- app/Controllers/BaseController.php | 2 +- app/Controllers/CreditsController.php | 3 +- app/Controllers/EpisodeCommentController.php | 1 + app/Controllers/EpisodeController.php | 15 +- app/Controllers/HomeController.php | 1 + ...MarkerController.php => MapController.php} | 4 +- app/Controllers/PageController.php | 3 +- app/Controllers/PodcastController.php | 3 + app/Controllers/PostController.php | 6 +- app/Helpers/page_helper.php | 2 +- app/Helpers/seo_helper.php | 275 ++++++++++++++++++ app/Language/en/Page.php | 19 +- app/Language/en/Podcast.php | 3 + app/Language/fr/Page.php | 19 +- composer.json | 3 +- composer.lock | 60 +++- themes/cp_admin/_layout.php | 2 + themes/cp_admin/episode/_sidebar.php | 2 +- themes/cp_admin/episode/publish.php | 2 +- themes/cp_admin/episode/publish_edit.php | 2 +- themes/cp_app/embed.php | 1 + themes/cp_app/episode/_layout.php | 5 +- .../cp_app/episode/_partials/preview_card.php | 2 +- themes/cp_app/episode/activity.php | 31 -- themes/cp_app/episode/comment.php | 23 +- themes/cp_app/episode/comments.php | 33 --- themes/cp_app/home.php | 7 +- themes/cp_app/page.php | 48 --- themes/cp_app/{ => pages}/_layout.php | 14 +- themes/cp_app/{ => pages}/credits.php | 2 +- themes/cp_app/{ => pages}/map.php | 10 +- themes/cp_app/pages/page.php | 11 + themes/cp_app/podcast/_layout.php | 5 +- themes/cp_app/podcast/about.php | 40 +-- themes/cp_app/podcast/activity.php | 27 +- themes/cp_app/podcast/episodes.php | 26 -- themes/cp_app/podcast/follow.php | 15 +- themes/cp_app/post/post.php | 23 +- themes/cp_app/post/remote_action.php | 18 +- themes/cp_auth/_layout.php | 2 + themes/cp_install/_layout.php | 4 +- 45 files changed, 449 insertions(+), 354 deletions(-) create mode 100644 app/Config/Embed.php rename app/Controllers/{MapMarkerController.php => MapController.php} (95%) create mode 100644 app/Helpers/seo_helper.php delete mode 100644 themes/cp_app/page.php rename themes/cp_app/{ => pages}/_layout.php (76%) rename themes/cp_app/{ => pages}/credits.php (98%) rename themes/cp_app/{ => pages}/map.php (85%) create mode 100644 themes/cp_app/pages/page.php diff --git a/app/Config/App.php b/app/Config/App.php index 2c5e100ff8..d547394793 100644 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -435,6 +435,8 @@ class App extends BaseConfig */ public string $siteName = 'Castopod'; + public string $siteTitleSeparator = ' | '; + public string $siteDescription = 'Castopod Host is an open-source hosting platform made for podcasters who want engage and interact with their audience.'; /** diff --git a/app/Config/Embed.php b/app/Config/Embed.php new file mode 100644 index 0000000000..0c97f86f08 --- /dev/null +++ b/app/Config/Embed.php @@ -0,0 +1,19 @@ +<?php + +declare(strict_types=1); + +namespace Config; + +use CodeIgniter\Config\BaseConfig; + +class Embed extends BaseConfig +{ + /** + * -------------------------------------------------------------------------- + * Embeddable player config + * -------------------------------------------------------------------------- + */ + public int $width = 600; + + public int $height = 144; +} diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 7d2ca19f73..161c02fa4a 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -180,10 +180,10 @@ $routes->group('@(:podcastHandle)', function ($routes): void { $routes->get('/credits', 'CreditsController', [ 'as' => 'credits', ]); -$routes->get('/map', 'MapMarkerController', [ +$routes->get('/map', 'MapController', [ 'as' => 'map', ]); -$routes->get('/episodes-markers', 'MapMarkerController::getEpisodesMarkers', [ +$routes->get('/episodes-markers', 'MapController::getEpisodesMarkers', [ 'as' => 'episodes-markers', ]); $routes->get('/pages/(:slug)', 'PageController/$1', [ diff --git a/app/Controllers/ActorController.php b/app/Controllers/ActorController.php index ff1dbae54b..b7233df4de 100644 --- a/app/Controllers/ActorController.php +++ b/app/Controllers/ActorController.php @@ -20,7 +20,7 @@ class ActorController extends FediverseActorController /** * @var string[] */ - protected $helpers = ['auth', 'svg', 'components', 'misc']; + protected $helpers = ['auth', 'svg', 'components', 'misc', 'seo']; public function follow(): string { @@ -34,6 +34,8 @@ class ActorController extends FediverseActorController if (! ($cachedView = cache($cacheName))) { helper(['form', 'components', 'svg']); $data = [ + // @phpstan-ignore-next-line + 'metatags' => get_follow_metatags($this->actor), 'actor' => $this->actor, ]; diff --git a/app/Controllers/BaseController.php b/app/Controllers/BaseController.php index 2d30eb3c56..312f1ec2fb 100644 --- a/app/Controllers/BaseController.php +++ b/app/Controllers/BaseController.php @@ -28,7 +28,7 @@ class BaseController extends Controller ResponseInterface $response, LoggerInterface $logger ): void { - $this->helpers = array_merge($this->helpers, ['auth', 'svg', 'components', 'misc']); + $this->helpers = array_merge($this->helpers, ['auth', 'svg', 'components', 'misc', 'seo']); // Do Not Edit This Line parent::initController($request, $response, $logger); diff --git a/app/Controllers/CreditsController.php b/app/Controllers/CreditsController.php index 816653dd15..3531b3e2e8 100644 --- a/app/Controllers/CreditsController.php +++ b/app/Controllers/CreditsController.php @@ -165,11 +165,12 @@ class CreditsController extends BaseController } $data = [ + 'metatags' => get_page_metatags($page), 'page' => $page, 'credits' => $credits, ]; - $found = view('credits', $data); + $found = view('pages/credits', $data); cache() ->save($cacheName, $found, DECADE); diff --git a/app/Controllers/EpisodeCommentController.php b/app/Controllers/EpisodeCommentController.php index f64c1db304..6bb1ef0e67 100644 --- a/app/Controllers/EpisodeCommentController.php +++ b/app/Controllers/EpisodeCommentController.php @@ -95,6 +95,7 @@ class EpisodeCommentController extends BaseController if (! ($cachedView = cache($cacheName))) { $data = [ + 'metatags' => get_episode_comment_metatags($this->comment), 'podcast' => $this->podcast, 'actor' => $this->actor, 'episode' => $this->episode, diff --git a/app/Controllers/EpisodeController.php b/app/Controllers/EpisodeController.php index 3a59f91a27..e79b9be5f0 100644 --- a/app/Controllers/EpisodeController.php +++ b/app/Controllers/EpisodeController.php @@ -77,6 +77,7 @@ class EpisodeController extends BaseController if (! ($cachedView = cache($cacheName))) { $data = [ + 'metatags' => get_episode_metatags($this->episode), 'podcast' => $this->podcast, 'episode' => $this->episode, ]; @@ -115,6 +116,7 @@ class EpisodeController extends BaseController if (! ($cachedView = cache($cacheName))) { $data = [ + 'metatags' => get_episode_metatags($this->episode), 'podcast' => $this->podcast, 'episode' => $this->episode, ]; @@ -220,20 +222,21 @@ class EpisodeController extends BaseController $oembed->addChild('author_name', $this->podcast->title); $oembed->addChild('author_url', $this->podcast->link); $oembed->addChild('thumbnail', $this->episode->cover->large_url); - $oembed->addChild('thumbnail_width', config('Images')->podcastCoverSizes['large'][0]); - $oembed->addChild('thumbnail_height', config('Images')->podcastCoverSizes['large'][1]); + $oembed->addChild('thumbnail_width', (string) config('Images')->podcastCoverSizes['large'][0]); + $oembed->addChild('thumbnail_height', (string) config('Images')->podcastCoverSizes['large'][1]); $oembed->addChild( 'html', htmlentities( '<iframe src="' . $this->episode->embed_url . - '" width="100%" height="144" frameborder="0" scrolling="no"></iframe>', + '" width="100%" height="' . config('Embed')->height . '" frameborder="0" scrolling="no"></iframe>', ), ); - $oembed->addChild('width', '600'); - $oembed->addChild('height', '144'); + $oembed->addChild('width', (string) config('Embed')->width); + $oembed->addChild('height', (string) config('Embed')->height); - return $this->response->setXML((string) $oembed); + // @phpstan-ignore-next-line + return $this->response->setXML($oembed); } /** diff --git a/app/Controllers/HomeController.php b/app/Controllers/HomeController.php index f19eb74d1e..aeaaff0ab7 100644 --- a/app/Controllers/HomeController.php +++ b/app/Controllers/HomeController.php @@ -36,6 +36,7 @@ class HomeController extends BaseController // default behavior: list all podcasts on home page $data = [ + 'metatags' => get_home_metatags(), 'podcasts' => $allPodcasts, ]; diff --git a/app/Controllers/MapMarkerController.php b/app/Controllers/MapController.php similarity index 95% rename from app/Controllers/MapMarkerController.php rename to app/Controllers/MapController.php index 5ead95979b..c4a6cd4bde 100644 --- a/app/Controllers/MapMarkerController.php +++ b/app/Controllers/MapController.php @@ -13,7 +13,7 @@ namespace App\Controllers; use App\Models\EpisodeModel; use CodeIgniter\HTTP\ResponseInterface; -class MapMarkerController extends BaseController +class MapController extends BaseController { public function index(): string { @@ -21,7 +21,7 @@ class MapMarkerController extends BaseController ->getLocale(); $cacheName = "page_map_{$locale}"; if (! ($found = cache($cacheName))) { - $found = view('map', [], [ + $found = view('pages/map', [], [ 'cache' => DECADE, 'cache_name' => $cacheName, ]); diff --git a/app/Controllers/PageController.php b/app/Controllers/PageController.php index f1e1bf2a79..3a251a7dca 100644 --- a/app/Controllers/PageController.php +++ b/app/Controllers/PageController.php @@ -40,10 +40,11 @@ class PageController extends BaseController $cacheName = "page-{$this->page->slug}"; if (! ($found = cache($cacheName))) { $data = [ + 'metatags' => get_page_metatags($this->page), 'page' => $this->page, ]; - $found = view('page', $data); + $found = view('pages/page', $data); // The page cache is set to a decade so it is deleted manually upon page update cache() diff --git a/app/Controllers/PodcastController.php b/app/Controllers/PodcastController.php index f330064d43..6d0b6c690e 100644 --- a/app/Controllers/PodcastController.php +++ b/app/Controllers/PodcastController.php @@ -80,6 +80,7 @@ class PodcastController extends BaseController if (! ($cachedView = cache($cacheName))) { $data = [ + 'metatags' => get_podcast_metatags($this->podcast, 'activity'), 'podcast' => $this->podcast, 'posts' => (new PostModel())->getActorPublishedPosts($this->podcast->actor_id), ]; @@ -125,6 +126,7 @@ class PodcastController extends BaseController if (! ($cachedView = cache($cacheName))) { $data = [ + 'metatags' => get_podcast_metatags($this->podcast, 'about'), 'podcast' => $this->podcast, ]; @@ -240,6 +242,7 @@ class PodcastController extends BaseController } $data = [ + 'metatags' => get_podcast_metatags($this->podcast, 'episodes'), 'podcast' => $this->podcast, 'episodesNav' => $episodesNavigation, 'activeQuery' => $activeQuery, diff --git a/app/Controllers/PostController.php b/app/Controllers/PostController.php index 69837feb0d..0edb02dca5 100644 --- a/app/Controllers/PostController.php +++ b/app/Controllers/PostController.php @@ -35,7 +35,7 @@ class PostController extends FediversePostController /** * @var string[] */ - protected $helpers = ['auth', 'fediverse', 'svg', 'components', 'misc']; + protected $helpers = ['auth', 'fediverse', 'svg', 'components', 'misc', 'seo']; public function _remap(string $method, string ...$params): mixed { @@ -81,6 +81,8 @@ class PostController extends FediversePostController if (! ($cachedView = cache($cacheName))) { $data = [ + // @phpstan-ignore-next-line + 'metatags' => get_post_metatags($this->post), 'post' => $this->post, 'podcast' => $this->podcast, ]; @@ -233,6 +235,8 @@ class PostController extends FediversePostController if (! ($cachedView = cache($cacheName))) { $data = [ + // @phpstan-ignore-next-line + 'metatags' => get_remote_actions_metatags($this->post, $action), 'podcast' => $this->podcast, 'actor' => $this->actor, 'post' => $this->post, diff --git a/app/Helpers/page_helper.php b/app/Helpers/page_helper.php index beb1eb27b7..7817a4d939 100644 --- a/app/Helpers/page_helper.php +++ b/app/Helpers/page_helper.php @@ -25,7 +25,7 @@ if (! function_exists('render_page_links')) { $links .= anchor(route_to('credits'), lang('Person.credits'), [ 'class' => 'px-2 py-1 underline hover:no-underline focus:ring-accent', ]); - $links .= anchor(route_to('map'), lang('Page.map'), [ + $links .= anchor(route_to('map'), lang('Page.map.title'), [ 'class' => 'px-2 py-1 underline hover:no-underline focus:ring-accent', ]); foreach ($pages as $page) { diff --git a/app/Helpers/seo_helper.php b/app/Helpers/seo_helper.php new file mode 100644 index 0000000000..36edf1cb2c --- /dev/null +++ b/app/Helpers/seo_helper.php @@ -0,0 +1,275 @@ +<?php + +declare(strict_types=1); + +use App\Entities\Actor; +use App\Entities\Episode; +use App\Entities\EpisodeComment; +use App\Entities\Page; +use App\Entities\Podcast; +use App\Entities\Post; +use Melbahja\Seo\MetaTags; +use Melbahja\Seo\Schema; +use Melbahja\Seo\Schema\Thing; + +/** + * @copyright 2021 Podlibre + * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 + * @link https://castopod.org/ + */ + +if (! function_exists('get_podcast_metatags')) { + function get_podcast_metatags(Podcast $podcast, string $page): string + { + $schema = new Schema( + new Thing('PodcastSeries', [ + 'name' => $podcast->title, + 'url' => url_to('podcast-activity', $podcast->handle), + 'image' => $podcast->cover->feed_url, + 'description' => $podcast->description, + 'webFeed' => $podcast->feed_url, + 'author' => new Thing('Person', [ + 'name' => $podcast->publisher, + ]), + ]) + ); + + $metatags = new MetaTags(); + + $metatags + ->title(' ' . $podcast->title . " (@{$podcast->handle})" . ' • ' . lang('Podcast.' . $page)) + ->description(htmlspecialchars($podcast->description)) + ->image((string) $podcast->cover->large_url) + ->canonical((string) current_url()) + ->og('image:width', (string) config('Images')->podcastCoverSizes['large'][0]) + ->og('image:height', (string) config('Images')->podcastCoverSizes['large'][1]) + ->og('locale', $podcast->language_code) + ->og('site_name', service('settings')->get('App.siteName')); + + if ($podcast->payment_pointer) { + $metatags->meta('monetization', $podcast->payment_pointer); + } + + return '<link type="application/rss+xml" rel="alternate" title="' . $podcast->title . '" href="' . $podcast->feed_url . '" />' . PHP_EOL . $metatags->__toString() . PHP_EOL . $schema->__toString(); + } +} + +if (! function_exists('get_episode_metatags')) { + function get_episode_metatags(Episode $episode): string + { + $schema = new Schema( + new Thing('PodcastEpisode', [ + 'url' => url_to('episode', $episode->podcast->handle, $episode->slug), + 'name' => $episode->title, + 'image' => $episode->cover->feed_url, + 'description' => $episode->description, + 'datePublished' => $episode->published_at->format(DATE_ISO8601), + 'timeRequired' => iso8601_duration($episode->audio_file_duration), + 'associatedMedia' => new Thing('MediaObject', [ + 'contentUrl' => $episode->audio_file_url, + ]), + 'partOfSeries' => new Thing('PodcastSeries', [ + 'name' => $episode->podcast->title, + 'url' => url_to('podcast-activity', $episode->podcast->handle), + ]), + ]) + ); + + $metatags = new MetaTags(); + + $metatags + ->title($episode->title) + ->description(htmlspecialchars($episode->description)) + ->image((string) $episode->cover->large_url, 'player') + ->canonical($episode->link) + ->og('site_name', service('settings')->get('App.siteName')) + ->og('image:width', (string) config('Images')->podcastCoverSizes['large'][0]) + ->og('image:height', (string) config('Images')->podcastCoverSizes['large'][1]) + ->og('locale', $episode->podcast->language_code) + ->og('audio', $episode->audio_file_opengraph_url) + ->og('audio:type', $episode->audio_file_mimetype) + ->meta('article:published_time', $episode->published_at->format(DATE_ISO8601)) + ->meta('article:modified_time', $episode->updated_at->format(DATE_ISO8601)) + ->twitter('audio:partner', $episode->podcast->publisher ?? '') + ->twitter('audio:artist_name', $episode->podcast->owner_name) + ->twitter('player', $episode->getEmbedUrl('light')) + ->twitter('player:width', (string) config('Embed')->width) + ->twitter('player:height', (string) config('Embed')->height); + + if ($episode->podcast->payment_pointer) { + $metatags->meta('monetization', $episode->podcast->payment_pointer); + } + + return $metatags->__toString() . PHP_EOL . '<link rel="alternate" type="application/json+oembed" href="' . base_url( + route_to('episode-oembed-json', $episode->podcast->handle, $episode->slug) + ) . '" title="' . $episode->title . ' oEmbed json" />' . PHP_EOL . '<link rel="alternate" type="text/xml+oembed" href="' . base_url( + route_to('episode-oembed-xml', $episode->podcast->handle, $episode->slug) + ) . '" title="' . $episode->title . ' oEmbed xml" />' . PHP_EOL . $schema->__toString(); + } +} + +if (! function_exists('get_post_metatags')) { + function get_post_metatags(Post $post): string + { + $socialMediaPosting = new Thing('SocialMediaPosting', [ + '@id' => url_to('post', $post->actor->username, $post->id), + 'datePublished' => $post->published_at->format(DATE_ISO8601), + 'author' => new Thing('Person', [ + 'name' => $post->actor->display_name, + 'url' => $post->actor->uri, + ]), + 'text' => $post->message, + ]); + + if ($post->episode_id !== null) { + $socialMediaPosting->__set('sharedContent', new Thing('Audio', [ + 'headline' => $post->episode->title, + 'url' => $post->episode->link, + 'author' => new Thing('Person', [ + 'name' => $post->episode->podcast->owner_name, + ]), + ])); + } elseif ($post->preview_card !== null) { + $socialMediaPosting->__set('sharedContent', new Thing('WebPage', [ + 'headline' => $post->preview_card->title, + 'url' => $post->preview_card->url, + 'author' => new Thing('Person', [ + 'name' => $post->preview_card->author_name, + ]), + ])); + } + + $schema = new Schema($socialMediaPosting); + + $metatags = new MetaTags(); + $metatags + ->title(lang('Post.title', [ + 'actorDisplayName' => $post->actor->display_name, + ])) + ->description($post->message) + ->image($post->actor->avatar_image_url) + ->canonical((string) current_url()) + ->og('site_name', service('settings')->get('App.siteName')); + + return $metatags->__toString() . PHP_EOL . $schema->__toString(); + } +} + +if (! function_exists('get_episode_comment_metatags')) { + function get_episode_comment_metatags(EpisodeComment $episodeComment): string + { + $schema = new Schema(new Thing('SocialMediaPosting', [ + '@id' => url_to( + 'episode-comment', + $episodeComment->actor->username, + $episodeComment->episode->slug, + $episodeComment->id + ), + 'datePublished' => $episodeComment->created_at->format(DATE_ISO8601), + 'author' => new Thing('Person', [ + 'name' => $episodeComment->actor->display_name, + 'url' => $episodeComment->actor->uri, + ]), + 'text' => $episodeComment->message, + 'upvoteCount' => $episodeComment->likes_count, + ])); + + $metatags = new MetaTags(); + $metatags + ->title(lang('Comment.title', [ + 'actorDisplayName' => $episodeComment->actor->display_name, + 'episodeTitle' => $episodeComment->episode->title, + ])) + ->description($episodeComment->message) + ->image($episodeComment->actor->avatar_image_url) + ->canonical((string) current_url()) + ->og('site_name', service('settings')->get('App.siteName')); + + return $metatags->__toString() . PHP_EOL . $schema->__toString(); + } +} + +if (! function_exists('get_follow_metatags')) { + function get_follow_metatags(Actor $actor): string + { + $metatags = new MetaTags(); + $metatags + ->title(lang('Podcast.followTitle', [ + 'actorDisplayName' => $actor->display_name, + ])) + ->description($actor->summary) + ->image($actor->avatar_image_url) + ->canonical((string) current_url()) + ->og('site_name', service('settings')->get('App.siteName')); + + return $metatags->__toString(); + } +} + +if (! function_exists('get_remote_actions_metatags')) { + function get_remote_actions_metatags(Post $post, string $action): string + { + $metatags = new MetaTags(); + $metatags + ->title(lang('Fediverse.' . $action . '.title', [ + 'actorDisplayName' => $post->actor->display_name, + ],)) + ->description($post->message) + ->image($post->actor->avatar_image_url) + ->canonical((string) current_url()) + ->og('site_name', service('settings')->get('App.siteName')); + + return $metatags->__toString(); + } +} + +if (! function_exists('get_home_metatags')) { + function get_home_metatags(): string + { + $metatags = new MetaTags(); + $metatags + ->title(service('settings')->get('App.siteName')) + ->description(service('settings')->get('App.siteDescription')) + ->image(service('settings')->get('App.siteIcon')['512']) + ->canonical((string) current_url()) + ->og('site_name', service('settings')->get('App.siteName')); + + return $metatags->__toString(); + } +} + +if (! function_exists('get_page_metatags')) { + function get_page_metatags(Page $page): string + { + $metatags = new MetaTags(); + $metatags + ->title( + $page->title . service('settings')->get('App.siteTitleSeparator') . service( + 'settings' + )->get('App.siteName') + ) + ->description(service('settings')->get('App.siteDescription')) + ->image(service('settings')->get('App.siteIcon')['512']) + ->canonical((string) current_url()) + ->og('site_name', service('settings')->get('App.siteName')); + + return $metatags->__toString(); + } +} + +if (! function_exists('iso8601_duration')) { + // From https://stackoverflow.com/a/40761380 + function iso8601_duration(float $seconds): string + { + $days = floor($seconds / 86400); + $seconds %= 86400; + + $hours = floor($seconds / 3600); + $seconds %= 3600; + + $minutes = floor($seconds / 60); + $seconds %= 60; + + return sprintf('P%dDT%dH%dM%dS', $days, $hours, $minutes, $seconds); + } +} diff --git a/app/Language/en/Page.php b/app/Language/en/Page.php index 592b878464..9a255bda06 100644 --- a/app/Language/en/Page.php +++ b/app/Language/en/Page.php @@ -10,21 +10,8 @@ declare(strict_types=1); return [ 'back_to_home' => 'Back to home', - 'page' => 'Page', - 'all_pages' => 'All pages', - 'create' => 'New page', - 'go_to_page' => 'Go to page', - 'edit' => 'Edit page', - 'delete' => 'Delete page', - 'form' => [ - 'title' => 'Title', - 'permalink' => 'Permalink', - 'content' => 'Content', - 'submit_create' => 'Create page', - 'submit_edit' => 'Save', + 'map' => [ + 'title' => 'Map', + 'description' => 'Discover podcast episodes on {siteName} that are placed on a map! Travel through the map and listen to episodes that talk about specific locations.', ], - 'messages' => [ - 'createSuccess' => 'The page “{pageTitle}†was created successfully!', - ], - 'map' => 'Map', ]; diff --git a/app/Language/en/Podcast.php b/app/Language/en/Podcast.php index d8c0a20e48..7528a9a3e2 100644 --- a/app/Language/en/Podcast.php +++ b/app/Language/en/Podcast.php @@ -28,8 +28,11 @@ return [ other {<span class="font-semibold">#</span> posts} }', 'activity' => 'Activity', + 'activity_title' => '{podcastTitle} news & activity', 'episodes' => 'Episodes', + 'episodes_title' => 'Episodes of {podcastTitle}', 'about' => 'About', + 'about_title' => 'About {podcastTitle}', 'sponsor_title' => 'Enjoying the show?', 'sponsor' => 'Sponsor', 'funding_links' => 'Funding links for {podcastTitle}', diff --git a/app/Language/fr/Page.php b/app/Language/fr/Page.php index 220b1a73ef..8ad3f5d113 100644 --- a/app/Language/fr/Page.php +++ b/app/Language/fr/Page.php @@ -10,21 +10,8 @@ declare(strict_types=1); return [ 'back_to_home' => 'Retour à l’accueil', - 'page' => 'Page', - 'all_pages' => 'Toutes les pages', - 'create' => 'Créer une page', - 'go_to_page' => 'Aller à la page', - 'edit' => 'Modifier la page', - 'delete' => 'Supprimer la page', - 'form' => [ - 'title' => 'Titre', - 'permalink' => 'Lien permanent', - 'content' => 'Contenu', - 'submit_create' => 'Créer la page', - 'submit_edit' => 'Enregistrer', + 'map' => [ + 'title' => 'Cartographie', + 'description' => 'Découvrez des épisodes de podcast placés sur une carte avec {siteName} ! Voyagez sur une carte du monde et écoutez des épisodes mentionnant des lieux spécifiques.', ], - 'messages' => [ - 'createSuccess' => 'La page {pageTitle} a été créée avec succès !', - ], - 'map' => 'Cartographie', ]; diff --git a/composer.json b/composer.json index b7dffcaf40..a2f505883c 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ "michalsn/codeigniter4-uuid": "dev-develop", "essence/essence": "^3.5.4", "codeigniter4/settings": "dev-develop", - "chrisjean/php-ico": "^1.0" + "chrisjean/php-ico": "^1.0", + "melbahja/seo": "^2.0" }, "require-dev": { "mikey179/vfsstream": "^v1.6.8", diff --git a/composer.lock b/composer.lock index 82b1287a71..fabdde839b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f35a050323bdc632cd550f9d13f0679c", + "content-hash": "c0a25c3d11c806b4bc62eafb22902bc8", "packages": [ { "name": "brick/math", @@ -1120,6 +1120,64 @@ }, "time": "2020-11-02T17:00:53+00:00" }, + { + "name": "melbahja/seo", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/melbahja/seo.git", + "reference": "a42500223cb532d4069e85097cc5b5e6ee402de1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/melbahja/seo/zipball/a42500223cb532d4069e85097cc5b5e6ee402de1", + "reference": "a42500223cb532d4069e85097cc5b5e6ee402de1", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-xml": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Melbahja\\Seo\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": ["MIT"], + "authors": [ + { + "name": "Mohamed ELbahja", + "email": "mohamed@elbahja.me", + "homepage": "https://elbahja.me", + "role": "Developer" + } + ], + "description": "Simple PHP library to help developers 🻠do better on-page SEO optimization", + "keywords": [ + "PHP7", + "meta tags", + "open graph", + "php7.1", + "schema.org", + "search engine optimization", + "seo", + "sitemap index", + "sitemap.xml", + "sitemaps", + "twitter tags" + ], + "support": { + "issues": "https://github.com/melbahja/seo/issues", + "source": "https://github.com/melbahja/seo/tree/v2.0.0" + }, + "time": "2021-10-26T00:36:49+00:00" + }, { "name": "michalsn/codeigniter4-uuid", "version": "dev-develop", diff --git a/themes/cp_admin/_layout.php b/themes/cp_admin/_layout.php index 11e602136a..9cc6cdc062 100644 --- a/themes/cp_admin/_layout.php +++ b/themes/cp_admin/_layout.php @@ -4,6 +4,8 @@ <head> <meta charset="UTF-8"/> + <meta name="robots" content="noindex"> + <title><?= $this->renderSection('title') ?> | Castopod Admin</title> <meta name="description" content="Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience."/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> diff --git a/themes/cp_admin/episode/_sidebar.php b/themes/cp_admin/episode/_sidebar.php index 96d21e343f..af4c2e3bfa 100644 --- a/themes/cp_admin/episode/_sidebar.php +++ b/themes/cp_admin/episode/_sidebar.php @@ -16,7 +16,7 @@ $podcastNavigation = [ /> <span class="flex-1 w-full px-2 text-xs font-semibold truncate" title="<?= $podcast->title ?>"><?= $podcast->title ?></span> </a> -<div class="flex items-center px-4 py-2 border-t border-b border-navigation"> +<div class="flex items-center px-4 py-2 border-y border-navigation"> <img src="<?= $episode->cover->thumbnail_url ?>" alt="<?= $episode->title ?>" diff --git a/themes/cp_admin/episode/publish.php b/themes/cp_admin/episode/publish.php index 692483e4bc..a0397e536f 100644 --- a/themes/cp_admin/episode/publish.php +++ b/themes/cp_admin/episode/publish.php @@ -39,7 +39,7 @@ <div class="px-4 mb-2"> <Forms.Textarea name="message" placeholder="<?= lang('Episode.publish_form.message_placeholder') ?>" autofocus="" rows="2" /> </div> - <div class="flex border-t border-b"> + <div class="flex border-y"> <img src="<?= $episode->cover ->thumbnail_url ?>" alt="<?= $episode->title ?>" class="w-24 h-24" /> <div class="flex flex-col flex-1"> diff --git a/themes/cp_admin/episode/publish_edit.php b/themes/cp_admin/episode/publish_edit.php index 17f0162cc8..6e38016e91 100644 --- a/themes/cp_admin/episode/publish_edit.php +++ b/themes/cp_admin/episode/publish_edit.php @@ -41,7 +41,7 @@ <div class="px-4 mb-2"> <Forms.Textarea name="message" placeholder="<?= lang('Episode.publish_form.message_placeholder') ?>" autofocus="" value="<?= $post->message ?>" rows="2" /> </div> - <div class="flex border-t border-b"> + <div class="flex border-y"> <img src="<?= $episode->cover ->thumbnail_url ?>" alt="<?= $episode->title ?>" class="w-24 h-24" /> <div class="flex flex-col flex-1"> diff --git a/themes/cp_app/embed.php b/themes/cp_app/embed.php index 0f4b346473..e81ac9e962 100644 --- a/themes/cp_app/embed.php +++ b/themes/cp_app/embed.php @@ -4,6 +4,7 @@ <head> <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title><?= $episode->title ?></title> <meta name="description" content="<?= htmlspecialchars( $episode->description, diff --git a/themes/cp_app/episode/_layout.php b/themes/cp_app/episode/_layout.php index 0142abdc80..16d340d2ef 100644 --- a/themes/cp_app/episode/_layout.php +++ b/themes/cp_app/episode/_layout.php @@ -12,10 +12,7 @@ <link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> <link rel="manifest" href="<?= route_to('webmanifest') ?>"> - <?= $this->renderSection('meta-tags') ?> - <?php if ($podcast->payment_pointer): ?> - <meta name="monetization" content="<?= $podcast->payment_pointer ?>" /> - <?php endif; ?> + <?= $metatags ?> <?= service('vite') ->asset('styles/index.css', 'css') ?> diff --git a/themes/cp_app/episode/_partials/preview_card.php b/themes/cp_app/episode/_partials/preview_card.php index c0966e5686..d429d938df 100644 --- a/themes/cp_app/episode/_partials/preview_card.php +++ b/themes/cp_app/episode/_partials/preview_card.php @@ -1,4 +1,4 @@ -<div class="flex items-center border-t border-b border-subtle"> +<div class="flex items-center border-y border-subtle"> <div class="relative"> <time class="absolute px-1 text-sm font-semibold text-white rounded bg-black/75 bottom-2 right-2" datetime="PT<?= $episode->audio_file_duration ?>S"> <?= format_duration($episode->audio_file_duration) ?> diff --git a/themes/cp_app/episode/activity.php b/themes/cp_app/episode/activity.php index c0ca08aa77..5c9a8d7e00 100644 --- a/themes/cp_app/episode/activity.php +++ b/themes/cp_app/episode/activity.php @@ -1,36 +1,5 @@ <?= $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->cover->large_url ?>" /> -<meta property="og:image:width" content="<?= config('Images') - ->podcastCoverSizes['large'][0] ?>" /> -<meta property="og:image:height" content="<?= config('Images')->podcastCoverSizes['large'][1] ?>" /> -<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->cover->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->getEmbedUrl('light') ?>" /> -<meta name="twitter:player:width" content="600" /> -<meta name="twitter:player:height" content="200" /> -<?= $this->endSection() ?> - <?= $this->section('content') ?> <?php if (can_user_interact()): ?> diff --git a/themes/cp_app/episode/comment.php b/themes/cp_app/episode/comment.php index 37c1e2364c..446bfb143f 100644 --- a/themes/cp_app/episode/comment.php +++ b/themes/cp_app/episode/comment.php @@ -1,30 +1,13 @@ <?= $this->extend('episode/_layout') ?> -<?= $this->section('meta-tags') ?> - <title><?= lang('Comment.title', [ - 'actorDisplayName' => $comment->actor->display_name, - ]) ?></title> - <meta name="description" content="<?= $comment->message ?>"/> - <meta property="og:title" content="<?= lang('Comment.title', [ - 'actorDisplayName' => $comment->actor->display_name, - ]) ?>"/> - <meta property="og:locale" content="<?= service( - 'request', - )->getLocale() ?>" /> - <meta property="og:site_name" content="<?= $comment->actor->display_name ?>" /> - <meta property="og:url" content="<?= current_url() ?>" /> - <meta property="og:image" content="<?= $comment->actor->avatar_image_url ?>" /> - <meta property="og:description" content="<?= $comment->message ?>" /> -<?= $this->endSection() ?> - <?= $this->section('content') ?> <div class="max-w-2xl px-6 mx-auto"> <nav class="mb-2"> <a href="<?= route_to('episode', $podcast->handle, $episode->slug) ?>" class="inline-flex items-center px-4 py-2 text-sm focus:ring-accent"><?= icon( - 'arrow-left', - 'mr-2 text-lg', - ) . lang('Comment.back_to_comments') ?></a> + 'arrow-left', + 'mr-2 text-lg', +) . lang('Comment.back_to_comments') ?></a> </nav> <div class="pb-12"> <?= $this->include('episode/_partials/comment_with_replies') ?> diff --git a/themes/cp_app/episode/comments.php b/themes/cp_app/episode/comments.php index 873e166874..be862832c7 100644 --- a/themes/cp_app/episode/comments.php +++ b/themes/cp_app/episode/comments.php @@ -1,38 +1,5 @@ <?= $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->cover->large_url ?>" /> -<meta property="og:image:width" content="<?= config('Images') - ->podcastCoverSizes['large'][0] ?>" /> -<meta property="og:image:height" content="<?= config('Images')->podcastCoverSizes['large'][1] ?>" /> -<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->cover->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->getEmbedUrl('light') ?>" /> -<meta name="twitter:player:width" content="600" /> -<meta name="twitter:player:height" content="200" /> -<?= $this->endSection() ?> - <?= $this->section('content') ?> <?php if (can_user_interact()): ?> diff --git a/themes/cp_app/home.php b/themes/cp_app/home.php index f15b2b9e60..708cfe331a 100644 --- a/themes/cp_app/home.php +++ b/themes/cp_app/home.php @@ -15,12 +15,7 @@ <link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> <link rel="manifest" href="<?= route_to('webmanifest') ?>"> - <meta property="og:title" content="<?= service('settings') - ->get('App.siteName') ?>" /> - <meta property="og:description" content="<?= service('settings') - ->get('App.siteDescription') ?>" /> - <meta property="og:site_name" content="<?= service('settings') - ->get('App.siteName') ?>" /> + <?= $metatags ?> <?= service('vite') ->asset('styles/index.css', 'css') ?> diff --git a/themes/cp_app/page.php b/themes/cp_app/page.php deleted file mode 100644 index 148f3152fc..0000000000 --- a/themes/cp_app/page.php +++ /dev/null @@ -1,48 +0,0 @@ -<?= helper('page') ?> -<!DOCTYPE html> -<html lang="<?= service('request') - ->getLocale() ?>"> - -<head> - <meta charset="UTF-8"/> - <title><?= $page->title ?></title> - <meta name="viewport" content="width=device-width, initial-scale=1.0"/> - <link rel="icon" type="image/x-icon" href="<?= service('settings') - ->get('App.siteIcon')['ico'] ?>" /> - <link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> - <link rel="manifest" href="<?= route_to('webmanifest') ?>"> - <?= 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-base theme-<?= service('settings') - ->get('App.theme') ?>"> - <?php if (service('authentication')->check()): ?> - <?= $this->include('_admin_navbar') ?> - <?php endif; ?> - - <header class="py-8 border-b bg-elevated border-subtle"> - <div class="container flex flex-col items-start px-2 py-4 mx-auto"> - <a href="<?= route_to('home') ?>" - class="inline-flex items-center mb-2 focus:ring-accent"><?= icon( - 'arrow-left', - 'mr-2', - ) . lang('Page.back_to_home') ?></a> - <Heading tagName="h1" class="text-3xl font-semibold"><?= $page->title ?></Heading> - </div> - </header> - <main class="container flex-1 px-4 py-10 mx-auto"> - <div class="prose prose-brand"> - <?= $page->content_html ?> - </div> - </main> - <footer class="container flex justify-between px-2 py-4 mx-auto text-sm text-right border-t border-subtle"> - <?= render_page_links() ?> - <small><?= lang('Common.powered_by', [ - 'castopod' => - '<a class="inline-flex font-semibold hover:underline focus:ring-accent" 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/_layout.php b/themes/cp_app/pages/_layout.php similarity index 76% rename from themes/cp_app/_layout.php rename to themes/cp_app/pages/_layout.php index d80ecb993a..30f9177c76 100644 --- a/themes/cp_app/_layout.php +++ b/themes/cp_app/pages/_layout.php @@ -5,21 +5,13 @@ <head> <meta charset="UTF-8"/> - <title><?= $this->renderSection('title') ?></title> - <meta name="description" content="<?= service('settings') - ->get('App.siteDescription') ?>"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <link rel="icon" type="image/x-icon" href="<?= service('settings') ->get('App.siteIcon')['ico'] ?>" /> <link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> <link rel="manifest" href="<?= route_to('webmanifest') ?>"> - <meta property="og:title" content="<?= service('settings') - ->get('App.siteName') ?>" /> - <meta property="og:description" content="<?= service('settings') - ->get('App.siteDescription') ?>" /> - <meta property="og:site_name" content="<?= service('settings') - ->get('App.siteName') ?>" /> + <?= $metatags ?> <?= service('vite') ->asset('styles/index.css', 'css') ?> @@ -42,9 +34,7 @@ 'arrow-left', 'mr-2', ) . lang('Page.back_to_home') ?></a> - <Heading tagName="h1" size="large"><?= isset($page) - ? $page->title - : 'Castopod' ?></Heading> + <Heading tagName="h1" size="large"><?= $page->title ?></Heading> </div> </header> <main class="container flex-1 px-4 py-6 mx-auto"> diff --git a/themes/cp_app/credits.php b/themes/cp_app/pages/credits.php similarity index 98% rename from themes/cp_app/credits.php rename to themes/cp_app/pages/credits.php index 44127527bf..a050bc1265 100644 --- a/themes/cp_app/credits.php +++ b/themes/cp_app/pages/credits.php @@ -1,4 +1,4 @@ -<?= $this->extend('_layout') ?> +<?= $this->extend('pages/_layout') ?> <?= $this->section('title') ?> <?= lang('Person.credits') ?> diff --git a/themes/cp_app/map.php b/themes/cp_app/pages/map.php similarity index 85% rename from themes/cp_app/map.php rename to themes/cp_app/pages/map.php index f064a54fdf..7f7b966103 100644 --- a/themes/cp_app/map.php +++ b/themes/cp_app/pages/map.php @@ -5,13 +5,17 @@ <head> <meta charset="UTF-8"/> - <title><?= lang('Page.map') ?></title> - <meta name="description" content="Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience."/> + <title><?= lang('Page.map.title') . service('settings')->get('App.siteTitleSeparator') . service('settings')->get('App.siteName') ?></title> + <meta name="description" content="<?= lang('Page.map.description', [ + 'siteName' => service('settings') + ->get('App.siteName'), + ]) ?>"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <link rel="icon" type="image/x-icon" href="<?= service('settings') ->get('App.siteIcon')['ico'] ?>" /> <link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> <link rel="manifest" href="<?= route_to('webmanifest') ?>"> + <?= service('vite') ->asset('styles/index.css', 'css') ?> <?= service('vite') @@ -33,7 +37,7 @@ 'arrow-left', 'mr-2', ) . lang('Page.back_to_home') ?></a> - <Heading tagName="h1" size="large"><?= lang('Page.map') ?></Heading> + <Heading tagName="h1" size="large"><?= lang('Page.map.title') ?></Heading> </div> </header> <main class="flex-1 w-full h-full"> diff --git a/themes/cp_app/pages/page.php b/themes/cp_app/pages/page.php new file mode 100644 index 0000000000..430d75941e --- /dev/null +++ b/themes/cp_app/pages/page.php @@ -0,0 +1,11 @@ +<?= $this->extend('pages/_layout') ?> + +<?= $this->section('title') ?> +<?= lang('Person.credits') ?> +<?= $this->endSection() ?> + +<?= $this->section('content') ?> + <div class="prose prose-brand"> + <?= $page->content_html ?> + </div> +<?= $this->endSection() ?> \ No newline at end of file diff --git a/themes/cp_app/podcast/_layout.php b/themes/cp_app/podcast/_layout.php index cf38f6d0ef..4c4c812bdc 100644 --- a/themes/cp_app/podcast/_layout.php +++ b/themes/cp_app/podcast/_layout.php @@ -12,10 +12,7 @@ <link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> <link rel="manifest" href="<?= route_to('webmanifest') ?>"> - <?= $this->renderSection('meta-tags') ?> - <?php if ($podcast->payment_pointer): ?> - <meta name="monetization" content="<?= $podcast->payment_pointer ?>" /> - <?php endif; ?> + <?= $metatags ?> <?= service('vite') ->asset('styles/index.css', 'css') ?> diff --git a/themes/cp_app/podcast/about.php b/themes/cp_app/podcast/about.php index ce00cbb117..0beb5e1e48 100644 --- a/themes/cp_app/podcast/about.php +++ b/themes/cp_app/podcast/about.php @@ -1,33 +1,5 @@ <?= $this->extend('podcast/_layout') ?> -<?= $this->section('meta-tags') ?> -<!-- TODO: --> - -<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="icon" type="image/x-icon" href="<?= service('settings') - ->get('App.siteIcon')['ico'] ?>" /> -<link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> -<link rel="manifest" href="<?= route_to('webmanifest') ?>"> -<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->cover->large_url ?>" /> -<meta property="og:image:width" content="<?= config('Images')->podcastCoverSizes['large'][0] ?>" /> -<meta property="og:image:height" content="<?= config('Images')->podcastCoverSizes['large'][1] ?>" /> -<meta name="twitter:card" content="summary_large_image" /> - -<?= service('vite') - ->asset('styles/index.css', 'css') ?> -<?= $this->endSection() ?> - <?= $this->section('content') ?> <div class="px-2 sm:px-4"> @@ -35,14 +7,14 @@ <div class="flex gap-x-4 gap-y-2"> <span class="px-2 py-1 text-sm font-semibold border rounded-sm border-subtle bg-highlight"> <?= lang( - 'Podcast.category_options.' . $podcast->category->code, - ) ?> + '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 border rounded-sm border-subtle bg-highlight"> <?= lang( - 'Podcast.category_options.' . $other_category->code, - ) ?> + 'Podcast.category_options.' . $other_category->code, +) ?> </span> <?php endforeach; ?> </div> @@ -55,8 +27,8 @@ <?php foreach ($podcast->persons as $person): ?> <img src="<?= $person->avatar->thumbnail_url ?>" alt="<?= $person->full_name ?>" class="object-cover w-8 h-8 -ml-5 border-2 rounded-full border-background-base last:ml-0" /> <?php $i++; if ($i === 3) { - break; - }?> + break; +}?> <?php endforeach; ?> </div> <?= lang('Podcast.persons', [ diff --git a/themes/cp_app/podcast/activity.php b/themes/cp_app/podcast/activity.php index a30afc4dfd..4ca9890365 100644 --- a/themes/cp_app/podcast/activity.php +++ b/themes/cp_app/podcast/activity.php @@ -1,31 +1,5 @@ <?= $this->extend('podcast/_layout') ?> -<?= $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="icon" type="image/x-icon" href="<?= service('settings') - ->get('App.siteIcon')['ico'] ?>" /> -<link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> -<link rel="manifest" href="<?= route_to('webmanifest') ?>"> -<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->cover->large_url ?>" /> -<meta property="og:image:width" content="<?= config('Images')->podcastCoverSizes['large'][0] ?>" /> -<meta property="og:image:height" content="<?= config('Images')->podcastCoverSizes['large'][1] ?>" /> -<meta name="twitter:card" content="summary_large_image" /> - -<?= service('vite') - ->asset('styles/index.css', 'css') ?> -<?= $this->endSection() ?> - <?= $this->section('content') ?> <?php if (can_user_interact()): ?> @@ -53,6 +27,7 @@ <hr class="my-4 border-subtle"> <?php endif; ?> + <div class="flex flex-col gap-y-4"> <?php foreach ($posts as $key => $post): ?> <?php if ($post->reblog_of_id !== null): ?> diff --git a/themes/cp_app/podcast/episodes.php b/themes/cp_app/podcast/episodes.php index 7e5b01f428..1db4ec29a8 100644 --- a/themes/cp_app/podcast/episodes.php +++ b/themes/cp_app/podcast/episodes.php @@ -1,31 +1,5 @@ <?= $this->extend('podcast/_layout') ?> -<?= $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="icon" type="image/x-icon" href="<?= service('settings') - ->get('App.siteIcon')['ico'] ?>" /> -<link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> -<link rel="manifest" href="<?= route_to('webmanifest') ?>"> -<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->cover->large_url ?>" /> -<meta property="og:image:width" content="<?= config('Images')->podcastCoverSizes['large'][0] ?>" /> -<meta property="og:image:height" content="<?= config('Images')->podcastCoverSizes['large'][1] ?>" /> -<meta name="twitter:card" content="summary_large_image" /> - -<?= service('vite') - ->asset('styles/index.css', 'css') ?> -<?= $this->endSection() ?> - <?= $this->section('content') ?> <?php if ($episodes): ?> diff --git a/themes/cp_app/podcast/follow.php b/themes/cp_app/podcast/follow.php index c491d46e09..0425544dee 100644 --- a/themes/cp_app/podcast/follow.php +++ b/themes/cp_app/podcast/follow.php @@ -12,19 +12,7 @@ <link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> <link rel="manifest" href="<?= route_to('webmanifest') ?>"> - <title><?= lang('Podcast.followTitle', [ - 'actorDisplayName' => $actor->display_name, - ]) ?></title> - <meta name="description" content="<?= $actor->summary ?>"/> - <meta property="og:title" content="<?= lang('Podcast.followTitle', [ - 'actorDisplayName' => $actor->display_name, - ]) ?>"/> - <meta property="og:locale" content="<?= service( - 'request', - )->getLocale() ?>" /> - <meta property="og:url" content="<?= current_url() ?>" /> - <meta property="og:image" content="<?= $actor->avatar_image_url ?>" /> - <meta property="og:description" content="<?= $actor->summary ?>" /> + <?= $metatags ?> <?= service('vite') ->asset('styles/index.css', 'css') ?> @@ -32,7 +20,6 @@ ->asset('js/podcast.ts', 'js') ?> </head> - <body class="flex flex-col min-h-screen bg-base theme-<?= service('settings') ->get('App.theme') ?>"> <header class="flex flex-col items-center mb-8"> diff --git a/themes/cp_app/post/post.php b/themes/cp_app/post/post.php index 1605e3fbad..02c168bf73 100644 --- a/themes/cp_app/post/post.php +++ b/themes/cp_app/post/post.php @@ -1,29 +1,12 @@ <?= $this->extend('podcast/_layout') ?> -<?= $this->section('meta-tags') ?> - <title><?= lang('Post.title', [ - 'actorDisplayName' => $post->actor->display_name, - ]) ?></title> - <meta name="description" content="<?= $post->message ?>"/> - <meta property="og:title" content="<?= lang('Post.title', [ - 'actorDisplayName' => $post->actor->display_name, - ]) ?>"/> - <meta property="og:locale" content="<?= service( - 'request', - )->getLocale() ?>" /> - <meta property="og:site_name" content="<?= $post->actor->display_name ?>" /> - <meta property="og:url" content="<?= current_url() ?>" /> - <meta property="og:image" content="<?= $post->actor->avatar_image_url ?>" /> - <meta property="og:description" content="<?= $post->message ?>" /> -<?= $this->endSection() ?> - <?= $this->section('content') ?> <nav class="py-2"> <a href="<?= route_to('podcast-activity', $podcast->handle) ?>" class="inline-flex items-center px-4 py-2 text-sm focus:ring-accent"><?= icon( - 'arrow-left', - 'mr-2 text-lg', - ) . + 'arrow-left', + 'mr-2 text-lg', +) . lang('Post.back_to_actor_posts', [ 'actor' => $post->actor->display_name, ]) ?></a> diff --git a/themes/cp_app/post/remote_action.php b/themes/cp_app/post/remote_action.php index 2dd36fe500..c0d13fd468 100644 --- a/themes/cp_app/post/remote_action.php +++ b/themes/cp_app/post/remote_action.php @@ -10,23 +10,7 @@ <link rel="apple-touch-icon" href="<?= service('settings')->get('App.siteIcon')['180'] ?>"> <link rel="manifest" href="<?= route_to('webmanifest') ?>"> - <title><?= lang('Fediverse.' . $action . '.title', [ - 'actorDisplayName' => $post->actor->display_name, - ]) ?></title> - <meta name="description" content="<?= $post->message ?>"/> - <meta property="og:title" content="<?= lang( - 'Fediverse.' . $action . '.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 ?>" /> + <?= $metatags ?> <?= service('vite') ->asset('styles/index.css', 'css') ?> diff --git a/themes/cp_auth/_layout.php b/themes/cp_auth/_layout.php index 1ac2151e81..fe6c3bfafe 100644 --- a/themes/cp_auth/_layout.php +++ b/themes/cp_auth/_layout.php @@ -4,6 +4,8 @@ <head> <meta charset="UTF-8"/> + <meta name="robots" content="noindex"> + <title>Castopod Auth</title> <meta name="description" content="Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience."/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> diff --git a/themes/cp_install/_layout.php b/themes/cp_install/_layout.php index 0de02688cb..0f9ee0d941 100644 --- a/themes/cp_install/_layout.php +++ b/themes/cp_install/_layout.php @@ -3,7 +3,9 @@ <head> <meta charset="UTF-8"/> - <title>Castopod</title> + <meta name="robots" content="noindex"> + + <title>Castopod Install</title> <meta name="description" content="Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience."/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <link rel="icon" type="image/x-icon" href="<?= service('settings') -- GitLab