feat: add podcast banner field for each podcast + refactor images configuration
- rename image fields on podcast, episode and persons for better clarity - set different sizes config for podcast cover, banner and persons avatars - add tiny size for covers - fix responsive on admin forms
This commit is contained in:
parent
5c56f3e6f0
commit
4a8147bfbb
|
@ -32,42 +32,63 @@ class Images extends BaseConfig
|
|||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Uploaded images resizing sizes (in px)
|
||||
| Uploaded images sizes (in px)
|
||||
|--------------------------------------------------------------------------
|
||||
| The sizes listed below determine the resizing of images when uploaded.
|
||||
| All uploaded images are of 1:1 ratio (width and height are the same).
|
||||
*/
|
||||
|
||||
public int $thumbnailSize = 150;
|
||||
|
||||
public int $mediumSize = 320;
|
||||
|
||||
public int $largeSize = 1024;
|
||||
/**
|
||||
* Podcast cover image sizes
|
||||
*
|
||||
* Uploaded podcast covers are of 1:1 ratio (width and height are the same).
|
||||
*
|
||||
* Size of images linked in the rss feed (should be between 1400 and 3000). Size for ID3 tag cover art (should be
|
||||
* between 300 and 800)
|
||||
*
|
||||
* Array values are as follows: 'name' => [width, height]
|
||||
*
|
||||
* @var array<string, int[]>
|
||||
*/
|
||||
public array $podcastCoverSizes = [
|
||||
'tiny' => [40, 40],
|
||||
'thumbnail' => [150, 150],
|
||||
'medium' => [320, 320],
|
||||
'large' => [1024, 1024],
|
||||
'feed' => [1400, 1400],
|
||||
'id3' => [500, 500],
|
||||
];
|
||||
|
||||
/**
|
||||
* Size of images linked in the rss feed (should be between 1400 and 3000)
|
||||
* Podcast header cover image
|
||||
*
|
||||
* Uploaded podcast header covers are of 3:1 ratio
|
||||
*
|
||||
* Array values are as follows: 'name' => [width, height]
|
||||
*
|
||||
* @var array<string, int[]>
|
||||
*/
|
||||
public int $feedSize = 1400;
|
||||
public array $podcastBannerSizes = [
|
||||
'small' => [320, 128],
|
||||
'medium' => [960, 320],
|
||||
'large' => [1500, 500],
|
||||
];
|
||||
|
||||
public string $podcastBannerDefaultPath = 'castopod-banner-default.jpg';
|
||||
|
||||
public string $podcastBannerDefaultMimeType = 'image/jpeg';
|
||||
|
||||
/**
|
||||
* Size for ID3 tag cover art (should be between 300 and 800)
|
||||
* Person image
|
||||
*
|
||||
* Uploaded person images are of 1:1 ratio (width and height are the same).
|
||||
*
|
||||
* Array values are as follows: 'name' => [width, height]
|
||||
*
|
||||
* @var array<string, int[]>
|
||||
*/
|
||||
public int $id3Size = 500;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Uploaded images naming extensions
|
||||
|--------------------------------------------------------------------------
|
||||
| The properties listed below set the name extensions for the resized images
|
||||
*/
|
||||
|
||||
public string $thumbnailSuffix = '_thumbnail';
|
||||
|
||||
public string $mediumSuffix = '_medium';
|
||||
|
||||
public string $largeSuffix = '_large';
|
||||
|
||||
public string $feedSuffix = '_feed';
|
||||
|
||||
public string $id3Suffix = '_id3';
|
||||
public array $personAvatarSizes = [
|
||||
'tiny' => [40, 40],
|
||||
'thumbnail' => [150, 150],
|
||||
'medium' => [320, 320],
|
||||
];
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ class CreditsController extends BaseController
|
|||
$personId => [
|
||||
'full_name' => $credit->person->full_name,
|
||||
'thumbnail_url' =>
|
||||
$credit->person->image->thumbnail_url,
|
||||
$credit->person->avatar->thumbnail_url,
|
||||
'information_url' =>
|
||||
$credit->person->information_url,
|
||||
'roles' => [
|
||||
|
@ -87,7 +87,7 @@ class CreditsController extends BaseController
|
|||
$credits[$personGroup]['persons'][$personId] = [
|
||||
'full_name' => $credit->person->full_name,
|
||||
'thumbnail_url' =>
|
||||
$credit->person->image->thumbnail_url,
|
||||
$credit->person->avatar->thumbnail_url,
|
||||
'information_url' => $credit->person->information_url,
|
||||
'roles' => [
|
||||
$personRole => [
|
||||
|
|
|
@ -200,11 +200,11 @@ class EpisodeController extends BaseController
|
|||
'" width="100%" height="144" frameborder="0" scrolling="no"></iframe>',
|
||||
'width' => 600,
|
||||
'height' => 144,
|
||||
'thumbnail_url' => $this->episode->image->large_url,
|
||||
'thumbnail_url' => $this->episode->cover->large_url,
|
||||
'thumbnail_width' => config('Images')
|
||||
->largeSize,
|
||||
->podcastCoverSizes['large'][0],
|
||||
'thumbnail_height' => config('Images')
|
||||
->largeSize,
|
||||
->podcastCoverSizes['large'][1],
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -219,9 +219,9 @@ class EpisodeController extends BaseController
|
|||
$oembed->addChild('provider_url', $this->podcast->link);
|
||||
$oembed->addChild('author_name', $this->podcast->title);
|
||||
$oembed->addChild('author_url', $this->podcast->link);
|
||||
$oembed->addChild('thumbnail', $this->episode->image->large_url);
|
||||
$oembed->addChild('thumbnail_width', config('Images')->largeSize);
|
||||
$oembed->addChild('thumbnail_height', config('Images')->largeSize);
|
||||
$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(
|
||||
'html',
|
||||
htmlentities(
|
||||
|
|
|
@ -46,7 +46,7 @@ class MapMarkerController extends BaseController
|
|||
'location_url' => $episode->location->url,
|
||||
'episode_link' => $episode->link,
|
||||
'podcast_link' => $episode->podcast->link,
|
||||
'image_path' => $episode->image->thumbnail_url,
|
||||
'cover_path' => $episode->cover->thumbnail_url,
|
||||
'podcast_title' => $episode->podcast->title,
|
||||
'episode_title' => $episode->title,
|
||||
];
|
||||
|
|
|
@ -46,16 +46,28 @@ class AddPodcasts extends Migration
|
|||
'description_html' => [
|
||||
'type' => 'TEXT',
|
||||
],
|
||||
'image_path' => [
|
||||
'cover_path' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 255,
|
||||
],
|
||||
// constraint is 13 because the longest safe mimetype for images is image/svg+xml,
|
||||
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types
|
||||
'image_mimetype' => [
|
||||
'cover_mimetype' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 13,
|
||||
],
|
||||
'banner_path' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 255,
|
||||
'null' => true,
|
||||
'default' => null,
|
||||
],
|
||||
'banner_mimetype' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 13,
|
||||
'null' => true,
|
||||
'default' => null,
|
||||
],
|
||||
'language_code' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 2,
|
||||
|
|
|
@ -70,14 +70,14 @@ class AddEpisodes extends Migration
|
|||
'description_html' => [
|
||||
'type' => 'TEXT',
|
||||
],
|
||||
'image_path' => [
|
||||
'cover_path' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 255,
|
||||
'null' => true,
|
||||
],
|
||||
// constraint is 13 because the longest safe mimetype for images is image/svg+xml,
|
||||
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types
|
||||
'image_mimetype' => [
|
||||
'cover_mimetype' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 13,
|
||||
'null' => true,
|
||||
|
|
|
@ -42,14 +42,14 @@ class AddPersons extends Migration
|
|||
'The url to a relevant resource of information about the person, such as a homepage or third-party profile platform.',
|
||||
'null' => true,
|
||||
],
|
||||
'image_path' => [
|
||||
'avatar_path' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 255,
|
||||
'null' => true,
|
||||
],
|
||||
// constraint is 13 because the longest safe mimetype for images is image/svg+xml,
|
||||
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types
|
||||
'image_mimetype' => [
|
||||
'avatar_mimetype' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 13,
|
||||
'null' => true,
|
||||
|
|
|
@ -44,9 +44,9 @@ use RuntimeException;
|
|||
* @property string|null $description Holds text only description, striped of any markdown or html special characters
|
||||
* @property string $description_markdown
|
||||
* @property string $description_html
|
||||
* @property Image $image
|
||||
* @property string|null $image_path
|
||||
* @property string|null $image_mimetype
|
||||
* @property Image $cover
|
||||
* @property string|null $cover_path
|
||||
* @property string|null $cover_mimetype
|
||||
* @property File|null $transcript_file
|
||||
* @property string|null $transcript_file_url
|
||||
* @property string|null $transcript_file_path
|
||||
|
@ -98,7 +98,7 @@ class Episode extends Entity
|
|||
|
||||
protected string $embed_url;
|
||||
|
||||
protected Image $image;
|
||||
protected Image $cover;
|
||||
|
||||
protected ?string $description = null;
|
||||
|
||||
|
@ -153,8 +153,8 @@ class Episode extends Entity
|
|||
'audio_file_header_size' => 'integer',
|
||||
'description_markdown' => 'string',
|
||||
'description_html' => 'string',
|
||||
'image_path' => '?string',
|
||||
'image_mimetype' => '?string',
|
||||
'cover_path' => '?string',
|
||||
'cover_mimetype' => '?string',
|
||||
'transcript_file_path' => '?string',
|
||||
'transcript_file_remote_url' => '?string',
|
||||
'chapters_file_path' => '?string',
|
||||
|
@ -175,31 +175,36 @@ class Episode extends Entity
|
|||
];
|
||||
|
||||
/**
|
||||
* Saves an episode image
|
||||
* Saves an episode cover
|
||||
*/
|
||||
public function setImage(?Image $image = null): static
|
||||
public function setCover(?Image $cover = null): static
|
||||
{
|
||||
if ($image === null) {
|
||||
if ($cover === null) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Save image
|
||||
$image->saveImage('podcasts/' . $this->getPodcast()->handle, $this->attributes['slug']);
|
||||
$cover->saveImage(
|
||||
config('Images')
|
||||
->podcastCoverSizes,
|
||||
'podcasts/' . $this->getPodcast()->handle,
|
||||
$this->attributes['slug']
|
||||
);
|
||||
|
||||
$this->attributes['image_mimetype'] = $image->mimetype;
|
||||
$this->attributes['image_path'] = $image->path;
|
||||
$this->attributes['cover_mimetype'] = $cover->mimetype;
|
||||
$this->attributes['cover_path'] = $cover->path;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getImage(): Image
|
||||
public function getCover(): Image
|
||||
{
|
||||
if ($imagePath = $this->attributes['image_path']) {
|
||||
return new Image(null, $imagePath, $this->attributes['image_mimetype']);
|
||||
if ($coverPath = $this->attributes['cover_path']) {
|
||||
return new Image(null, $coverPath, $this->attributes['cover_mimetype']);
|
||||
}
|
||||
|
||||
return $this->getPodcast()
|
||||
->image;
|
||||
->cover;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,7 +13,6 @@ namespace App\Entities;
|
|||
use CodeIgniter\Entity\Entity;
|
||||
use CodeIgniter\Files\File;
|
||||
use Config\Images;
|
||||
use Config\Services;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
|
@ -24,16 +23,6 @@ use RuntimeException;
|
|||
* @property string $mimetype
|
||||
* @property string $path
|
||||
* @property string $url
|
||||
* @property string $thumbnail_path
|
||||
* @property string $thumbnail_url
|
||||
* @property string $medium_path
|
||||
* @property string $medium_url
|
||||
* @property string $large_path
|
||||
* @property string $large_url
|
||||
* @property string $feed_path
|
||||
* @property string $feed_url
|
||||
* @property string $id3_path
|
||||
* @property string $id3_url
|
||||
*/
|
||||
class Image extends Entity
|
||||
{
|
||||
|
@ -47,14 +36,14 @@ class Image extends Entity
|
|||
|
||||
protected string $extension;
|
||||
|
||||
protected string $mimetype;
|
||||
|
||||
public function __construct(?File $file, string $path = '', string $mimetype = '')
|
||||
{
|
||||
if ($file === null && $path === '') {
|
||||
throw new RuntimeException('File or path must be set to create an Image.');
|
||||
}
|
||||
|
||||
$this->config = config('Images');
|
||||
|
||||
$dirname = '';
|
||||
$filename = '';
|
||||
$extension = '';
|
||||
|
@ -81,6 +70,45 @@ class Image extends Entity
|
|||
$this->mimetype = $mimetype;
|
||||
}
|
||||
|
||||
public function __get($property)
|
||||
{
|
||||
// Convert to CamelCase for the method
|
||||
$method = 'get' . str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $property)));
|
||||
|
||||
// if a get* method exists for this property,
|
||||
// call that method to get this value.
|
||||
// @phpstan-ignore-next-line
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
$fileSuffix = '';
|
||||
if ($lastUnderscorePosition = strrpos($property, '_')) {
|
||||
$fileSuffix = '_' . substr($property, 0, $lastUnderscorePosition);
|
||||
}
|
||||
|
||||
$path = '';
|
||||
if ($this->dirname !== '.') {
|
||||
$path .= $this->dirname . '/';
|
||||
}
|
||||
$path .= $this->filename . $fileSuffix . '.' . $this->extension;
|
||||
|
||||
if (str_ends_with($property, 'url')) {
|
||||
helper('media');
|
||||
|
||||
return media_base_url($path);
|
||||
}
|
||||
|
||||
if (str_ends_with($property, 'path')) {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
public function getMimetype(): string
|
||||
{
|
||||
return $this->mimetype;
|
||||
}
|
||||
|
||||
public function getFile(): File
|
||||
{
|
||||
if ($this->file === null) {
|
||||
|
@ -90,104 +118,10 @@ class Image extends Entity
|
|||
return $this->file;
|
||||
}
|
||||
|
||||
public function getPath(): string
|
||||
{
|
||||
return $this->dirname . '/' . $this->filename . '.' . $this->extension;
|
||||
}
|
||||
|
||||
public function getUrl(): string
|
||||
{
|
||||
helper('media');
|
||||
|
||||
return media_base_url($this->path);
|
||||
}
|
||||
|
||||
public function getThumbnailPath(): string
|
||||
{
|
||||
return $this->dirname .
|
||||
'/' .
|
||||
$this->filename .
|
||||
$this->config->thumbnailSuffix .
|
||||
'.' .
|
||||
$this->extension;
|
||||
}
|
||||
|
||||
public function getThumbnailUrl(): string
|
||||
{
|
||||
helper('media');
|
||||
|
||||
return media_base_url($this->thumbnail_path);
|
||||
}
|
||||
|
||||
public function getMediumPath(): string
|
||||
{
|
||||
return $this->dirname .
|
||||
'/' .
|
||||
$this->filename .
|
||||
$this->config->mediumSuffix .
|
||||
'.' .
|
||||
$this->extension;
|
||||
}
|
||||
|
||||
public function getMediumUrl(): string
|
||||
{
|
||||
helper('media');
|
||||
|
||||
return media_base_url($this->medium_path);
|
||||
}
|
||||
|
||||
public function getLargePath(): string
|
||||
{
|
||||
return $this->dirname .
|
||||
'/' .
|
||||
$this->filename .
|
||||
$this->config->largeSuffix .
|
||||
'.' .
|
||||
$this->extension;
|
||||
}
|
||||
|
||||
public function getLargeUrl(): string
|
||||
{
|
||||
helper('media');
|
||||
|
||||
return media_base_url($this->large_path);
|
||||
}
|
||||
|
||||
public function getFeedPath(): string
|
||||
{
|
||||
return $this->dirname .
|
||||
'/' .
|
||||
$this->filename .
|
||||
$this->config->feedSuffix .
|
||||
'.' .
|
||||
$this->extension;
|
||||
}
|
||||
|
||||
public function getFeedUrl(): string
|
||||
{
|
||||
helper('media');
|
||||
|
||||
return media_base_url($this->feed_path);
|
||||
}
|
||||
|
||||
public function getId3Path(): string
|
||||
{
|
||||
return $this->dirname .
|
||||
'/' .
|
||||
$this->filename .
|
||||
$this->config->id3Suffix .
|
||||
'.' .
|
||||
$this->extension;
|
||||
}
|
||||
|
||||
public function getId3Url(): string
|
||||
{
|
||||
helper('media');
|
||||
|
||||
return media_base_url($this->id3_path);
|
||||
}
|
||||
|
||||
public function saveImage(string $dirname, string $filename): void
|
||||
/**
|
||||
* @param array<string, int[]> $sizes
|
||||
*/
|
||||
public function saveImage(array $sizes, string $dirname, string $filename): void
|
||||
{
|
||||
helper('media');
|
||||
|
||||
|
@ -196,37 +130,27 @@ class Image extends Entity
|
|||
|
||||
save_media($this->file, $this->dirname, $this->filename);
|
||||
|
||||
$imageService = Services::image();
|
||||
$imageService = service('image');
|
||||
|
||||
$thumbnailSize = $this->config->thumbnailSize;
|
||||
$mediumSize = $this->config->mediumSize;
|
||||
$largeSize = $this->config->largeSize;
|
||||
$feedSize = $this->config->feedSize;
|
||||
$id3Size = $this->config->id3Size;
|
||||
foreach ($sizes as $name => $size) {
|
||||
$pathProperty = $name . '_path';
|
||||
$imageService
|
||||
->withFile(media_path($this->path))
|
||||
->resize($size[0], $size[1])
|
||||
->save(media_path($this->{$pathProperty}));
|
||||
}
|
||||
}
|
||||
|
||||
$imageService
|
||||
->withFile(media_path($this->path))
|
||||
->resize($thumbnailSize, $thumbnailSize)
|
||||
->save(media_path($this->thumbnail_path));
|
||||
/**
|
||||
* @param array<string, int[]> $sizes
|
||||
*/
|
||||
public function delete(array $sizes): void
|
||||
{
|
||||
helper('media');
|
||||
|
||||
$imageService
|
||||
->withFile(media_path($this->path))
|
||||
->resize($mediumSize, $mediumSize)
|
||||
->save(media_path($this->medium_path));
|
||||
|
||||
$imageService
|
||||
->withFile(media_path($this->path))
|
||||
->resize($largeSize, $largeSize)
|
||||
->save(media_path($this->large_path));
|
||||
|
||||
$imageService
|
||||
->withFile(media_path($this->path))
|
||||
->resize($feedSize, $feedSize)
|
||||
->save(media_path($this->feed_path));
|
||||
|
||||
$imageService
|
||||
->withFile(media_path($this->path))
|
||||
->resize($id3Size, $id3Size)
|
||||
->save(media_path($this->id3_path));
|
||||
foreach (array_keys($sizes) as $name) {
|
||||
$pathProperty = $name . '_path';
|
||||
unlink(media_path($this->{$pathProperty}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,16 +19,16 @@ use RuntimeException;
|
|||
* @property string $full_name
|
||||
* @property string $unique_name
|
||||
* @property string|null $information_url
|
||||
* @property Image $image
|
||||
* @property string $image_path
|
||||
* @property string $image_mimetype
|
||||
* @property Image $avatar
|
||||
* @property string $avatar_path
|
||||
* @property string $avatar_mimetype
|
||||
* @property int $created_by
|
||||
* @property int $updated_by
|
||||
* @property object[]|null $roles
|
||||
*/
|
||||
class Person extends Entity
|
||||
{
|
||||
protected Image $image;
|
||||
protected Image $avatar;
|
||||
|
||||
protected ?int $podcast_id = null;
|
||||
|
||||
|
@ -47,8 +47,8 @@ class Person extends Entity
|
|||
'full_name' => 'string',
|
||||
'unique_name' => 'string',
|
||||
'information_url' => '?string',
|
||||
'image_path' => '?string',
|
||||
'image_mimetype' => '?string',
|
||||
'avatar_path' => '?string',
|
||||
'avatar_mimetype' => '?string',
|
||||
'podcast_id' => '?integer',
|
||||
'episode_id' => '?integer',
|
||||
'created_by' => 'integer',
|
||||
|
@ -56,32 +56,31 @@ class Person extends Entity
|
|||
];
|
||||
|
||||
/**
|
||||
* Saves a picture in `public/media/persons/`
|
||||
* Saves the person avatar in `public/media/persons/`
|
||||
*/
|
||||
public function setImage(?Image $image = null): static
|
||||
public function setAvatar(?Image $avatar = null): static
|
||||
{
|
||||
if ($image === null) {
|
||||
if ($avatar === null) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
helper('media');
|
||||
|
||||
// Save image
|
||||
$image->saveImage('persons', $this->attributes['unique_name']);
|
||||
$avatar->saveImage(config('Images')->personAvatarSizes, 'persons', $this->attributes['unique_name']);
|
||||
|
||||
$this->attributes['image_mimetype'] = $image->mimetype;
|
||||
$this->attributes['image_path'] = $image->path;
|
||||
$this->attributes['avatar_mimetype'] = $avatar->mimetype;
|
||||
$this->attributes['avatar_path'] = $avatar->path;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getImage(): Image
|
||||
public function getAvatar(): Image
|
||||
{
|
||||
if ($this->attributes['image_path'] === null) {
|
||||
if ($this->attributes['avatar_path'] === null) {
|
||||
return new Image(null, '/castopod-avatar-default.jpg', 'image/jpeg');
|
||||
}
|
||||
|
||||
return new Image(null, $this->attributes['image_path'], $this->attributes['image_mimetype']);
|
||||
return new Image(null, $this->attributes['avatar_path'], $this->attributes['avatar_mimetype']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -34,9 +34,12 @@ use RuntimeException;
|
|||
* @property string|null $description Holds text only description, striped of any markdown or html special characters
|
||||
* @property string $description_markdown
|
||||
* @property string $description_html
|
||||
* @property Image $image
|
||||
* @property string $image_path
|
||||
* @property string $image_mimetype
|
||||
* @property Image $cover
|
||||
* @property string $cover_path
|
||||
* @property string $cover_mimetype
|
||||
* @property Image|null $banner
|
||||
* @property string|null $banner_path
|
||||
* @property string|null $banner_mimetype
|
||||
* @property string $language_code
|
||||
* @property int $category_id
|
||||
* @property Category|null $category
|
||||
|
@ -84,7 +87,9 @@ class Podcast extends Entity
|
|||
|
||||
protected ?Actor $actor = null;
|
||||
|
||||
protected Image $image;
|
||||
protected Image $cover;
|
||||
|
||||
protected ?Image $banner;
|
||||
|
||||
protected ?string $description = null;
|
||||
|
||||
|
@ -145,8 +150,10 @@ class Podcast extends Entity
|
|||
'title' => 'string',
|
||||
'description_markdown' => 'string',
|
||||
'description_html' => 'string',
|
||||
'image_path' => 'string',
|
||||
'image_mimetype' => 'string',
|
||||
'cover_path' => 'string',
|
||||
'cover_mimetype' => 'string',
|
||||
'banner_path' => '?string',
|
||||
'banner_mimetype' => '?string',
|
||||
'language_code' => 'string',
|
||||
'category_id' => 'integer',
|
||||
'parental_advisory' => '?string',
|
||||
|
@ -189,22 +196,63 @@ class Podcast extends Entity
|
|||
}
|
||||
|
||||
/**
|
||||
* Saves a cover image to the corresponding podcast folder in `public/media/podcast_name/`
|
||||
* Saves a podcast cover to the corresponding podcast folder in `public/media/podcast_name/`
|
||||
*/
|
||||
public function setImage(Image $image): static
|
||||
public function setCover(Image $cover): static
|
||||
{
|
||||
// Save image
|
||||
$image->saveImage('podcasts/' . $this->attributes['handle'], 'cover');
|
||||
$cover->saveImage(config('Images')->podcastCoverSizes, 'podcasts/' . $this->attributes['handle'], 'cover');
|
||||
|
||||
$this->attributes['image_mimetype'] = $image->mimetype;
|
||||
$this->attributes['image_path'] = $image->path;
|
||||
$this->attributes['cover_path'] = $cover->path;
|
||||
$this->attributes['cover_mimetype'] = $cover->mimetype;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getImage(): Image
|
||||
public function getCover(): Image
|
||||
{
|
||||
return new Image(null, $this->image_path, $this->image_mimetype);
|
||||
return new Image(null, $this->cover_path, $this->cover_mimetype);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a podcast cover to the corresponding podcast folder in `public/media/podcast_name/`
|
||||
*/
|
||||
public function setBanner(?Image $banner): static
|
||||
{
|
||||
if ($banner === null) {
|
||||
$this->attributes['banner_path'] = null;
|
||||
$this->attributes['banner_mimetype'] = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Save image
|
||||
$banner->saveImage(
|
||||
config('Images')
|
||||
->podcastBannerSizes,
|
||||
'podcasts/' . $this->attributes['handle'],
|
||||
'banner'
|
||||
);
|
||||
|
||||
$this->attributes['banner_path'] = $banner->path;
|
||||
$this->attributes['banner_mimetype'] = $banner->mimetype;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBanner(): Image
|
||||
{
|
||||
if ($this->attributes['banner_path'] === null) {
|
||||
return new Image(
|
||||
null,
|
||||
config('Images')
|
||||
->podcastBannerDefaultPath,
|
||||
config('Images')
|
||||
->podcastBannerDefaultMimeType
|
||||
);
|
||||
}
|
||||
|
||||
return new Image(null, $this->banner_path, $this->banner_mimetype);
|
||||
}
|
||||
|
||||
public function getLink(): string
|
||||
|
|
|
@ -51,7 +51,7 @@ if (! function_exists('write_audio_file_tags')) {
|
|||
$tagwriter->tagformats = ['id3v2.4'];
|
||||
$tagwriter->tag_encoding = $TextEncoding;
|
||||
|
||||
$cover = new File(media_path($episode->image->id3_path));
|
||||
$cover = new File(media_path($episode->cover->id3_path));
|
||||
|
||||
$APICdata = file_get_contents($cover->getRealPath());
|
||||
|
||||
|
|
|
@ -56,8 +56,7 @@ if (! function_exists('get_rss_feed')) {
|
|||
|
||||
$itunesImage = $channel->addChild('image', null, $itunesNamespace);
|
||||
|
||||
// FIXME: This should be downsized to 1400x1400
|
||||
$itunesImage->addAttribute('href', $podcast->image->url);
|
||||
$itunesImage->addAttribute('href', $podcast->cover->feed_url);
|
||||
|
||||
$channel->addChild('language', $podcast->language_code);
|
||||
if ($podcast->location !== null) {
|
||||
|
@ -134,7 +133,7 @@ if (! function_exists('get_rss_feed')) {
|
|||
$podcastNamespace,
|
||||
);
|
||||
|
||||
$personElement->addAttribute('img', $person->image->medium_url);
|
||||
$personElement->addAttribute('img', $person->avatar->medium_url);
|
||||
|
||||
if ($person->information_url !== null) {
|
||||
$personElement->addAttribute('href', $person->information_url);
|
||||
|
@ -190,7 +189,7 @@ if (! function_exists('get_rss_feed')) {
|
|||
}
|
||||
|
||||
$image = $channel->addChild('image');
|
||||
$image->addChild('url', $podcast->image->feed_url);
|
||||
$image->addChild('url', $podcast->cover->feed_url);
|
||||
$image->addChild('title', $podcast->title, null, false);
|
||||
$image->addChild('link', $podcast->link);
|
||||
|
||||
|
@ -234,7 +233,7 @@ if (! function_exists('get_rss_feed')) {
|
|||
$item->addChild('duration', (string) $episode->audio_file_duration, $itunesNamespace);
|
||||
$item->addChild('link', $episode->link);
|
||||
$episodeItunesImage = $item->addChild('image', null, $itunesNamespace);
|
||||
$episodeItunesImage->addAttribute('href', $episode->image->feed_url);
|
||||
$episodeItunesImage->addAttribute('href', $episode->cover->feed_url);
|
||||
|
||||
$episode->parental_advisory &&
|
||||
$item->addChild(
|
||||
|
@ -300,7 +299,7 @@ if (! function_exists('get_rss_feed')) {
|
|||
htmlspecialchars(lang("PersonsTaxonomy.persons.{$role->group}.label", [], 'en')),
|
||||
);
|
||||
|
||||
$personElement->addAttribute('img', $person->image->medium_url);
|
||||
$personElement->addAttribute('img', $person->avatar->medium_url);
|
||||
|
||||
if ($person->information_url !== null) {
|
||||
$personElement->addAttribute('href', $person->information_url);
|
||||
|
|
|
@ -52,8 +52,8 @@ class PodcastEpisode extends ObjectType
|
|||
|
||||
$this->image = [
|
||||
'type' => 'Image',
|
||||
'mediaType' => $episode->image_mimetype,
|
||||
'url' => $episode->image->url,
|
||||
'mediaType' => $episode->cover_mimetype,
|
||||
'url' => $episode->cover->feed_url,
|
||||
];
|
||||
|
||||
// add audio file
|
||||
|
|
|
@ -75,8 +75,8 @@ class EpisodeModel extends Model
|
|||
'audio_file_header_size',
|
||||
'description_markdown',
|
||||
'description_html',
|
||||
'image_path',
|
||||
'image_mimetype',
|
||||
'cover_path',
|
||||
'cover_mimetype',
|
||||
'transcript_file_path',
|
||||
'transcript_file_remote_url',
|
||||
'chapters_file_path',
|
||||
|
|
|
@ -35,8 +35,8 @@ class PersonModel extends Model
|
|||
'full_name',
|
||||
'unique_name',
|
||||
'information_url',
|
||||
'image_path',
|
||||
'image_mimetype',
|
||||
'avatar_path',
|
||||
'avatar_mimetype',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
];
|
||||
|
|
|
@ -10,7 +10,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Entities\Actor;
|
||||
use App\Entities\Podcast;
|
||||
use CodeIgniter\Database\Query;
|
||||
use CodeIgniter\HTTP\URI;
|
||||
|
@ -41,8 +40,10 @@ class PodcastModel extends Model
|
|||
'description_html',
|
||||
'episode_description_footer_markdown',
|
||||
'episode_description_footer_html',
|
||||
'image_path',
|
||||
'image_mimetype',
|
||||
'cover_path',
|
||||
'cover_mimetype',
|
||||
'banner_path',
|
||||
'banner_mimetype',
|
||||
'language_code',
|
||||
'category_id',
|
||||
'parental_advisory',
|
||||
|
@ -91,7 +92,7 @@ class PodcastModel extends Model
|
|||
'handle' =>
|
||||
'required|regex_match[/^[a-zA-Z0-9\_]{1,32}$/]|is_unique[podcasts.handle,id,{id}]',
|
||||
'description_markdown' => 'required',
|
||||
'image_path' => 'required',
|
||||
'cover_path' => 'required',
|
||||
'language_code' => 'required',
|
||||
'category_id' => 'required',
|
||||
'owner_email' => 'required|valid_email',
|
||||
|
@ -454,13 +455,16 @@ class PodcastModel extends Model
|
|||
{
|
||||
$podcast = (new self())->getPodcastById(is_array($data['id']) ? $data['id'][0] : $data['id']);
|
||||
|
||||
$podcastActor = (new ActorModel())->find($podcast->actor_id);
|
||||
if ($podcast !== null) {
|
||||
$podcastActor = (new ActorModel())->find($podcast->actor_id);
|
||||
|
||||
$podcastActor->avatar_image_url = $podcast->image->thumbnail_url;
|
||||
$podcastActor->avatar_image_mimetype = $podcast->image_mimetype;
|
||||
|
||||
(new ActorModel())->update($podcast->actor_id, $podcastActor);
|
||||
if ($podcastActor) {
|
||||
$podcastActor->avatar_image_url = $podcast->cover->thumbnail_url;
|
||||
$podcastActor->avatar_image_mimetype = $podcast->cover_mimetype;
|
||||
|
||||
(new ActorModel())->update($podcast->actor_id, $podcastActor);
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
@ -477,14 +481,18 @@ class PodcastModel extends Model
|
|||
$actorModel = new ActorModel();
|
||||
$actor = $actorModel->getActorById($podcast->actor_id);
|
||||
|
||||
// update values
|
||||
$actor->display_name = $podcast->title;
|
||||
$actor->summary = $podcast->description_html;
|
||||
$actor->avatar_image_url = $podcast->image->thumbnail_url;
|
||||
$actor->avatar_image_mimetype = $podcast->image_mimetype;
|
||||
if ($actor !== null) {
|
||||
// update values
|
||||
$actor->display_name = $podcast->title;
|
||||
$actor->summary = $podcast->description_html;
|
||||
$actor->avatar_image_url = $podcast->cover->thumbnail_url;
|
||||
$actor->avatar_image_mimetype = $podcast->cover->mimetype;
|
||||
$actor->cover_image_url = $podcast->banner->large_url;
|
||||
$actor->cover_image_mimetype = $podcast->banner->mimetype;
|
||||
|
||||
if ($actor->hasChanged()) {
|
||||
$actorModel->update($actor->id, $actor);
|
||||
if ($actor->hasChanged()) {
|
||||
$actorModel->update($actor->id, $actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ const drawEpisodesMap = async (mapDivId: string, dataUrl: string) => {
|
|||
data[i].longitude,
|
||||
]).bindPopup(
|
||||
'<div class="flex min-w-max"><img src="' +
|
||||
data[i].image_path +
|
||||
data[i].cover_path +
|
||||
'" alt="' +
|
||||
data[i].episode_title +
|
||||
'" class="mr-2 rounded w-16 h-16" /><div class="flex flex-col"><h2 class="lg:text-base text-sm ! font-bold"><a href="' +
|
||||
|
|
|
@ -38,4 +38,19 @@
|
|||
hsla(0, 0%, 0%, 0.8) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.backdrop-gradient-pine {
|
||||
background-image: linear-gradient(
|
||||
180deg,
|
||||
hsla(172, 100%, 17%, 0) 0%,
|
||||
hsla(172, 100%, 17%, 0.034375) 16.36%,
|
||||
hsla(172, 100%, 17%, 0.125) 33.34%,
|
||||
hsla(172, 100%, 17%, 0.253125) 50.1%,
|
||||
hsla(172, 100%, 17%, 0.4) 65.75%,
|
||||
hsla(172, 100%, 17%, 0.546875) 79.43%,
|
||||
hsla(172, 100%, 17%, 0.675) 90.28%,
|
||||
hsla(172, 100%, 17%, 0.765625) 97.43%,
|
||||
hsla(172, 100%, 17%, 0.8) 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,9 +57,9 @@ class FileRules extends ValidationFileRules
|
|||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Checks an uploaded file to verify that the image ratio is of 1:1
|
||||
* Checks an uploaded image to verify that the ratio corresponds to the params
|
||||
*/
|
||||
public function is_image_squared(string $blank = null, string $params): bool
|
||||
public function is_image_ratio(string $blank = null, string $params): bool
|
||||
{
|
||||
// Grab the file name off the top of the $params
|
||||
// after we split it.
|
||||
|
@ -79,12 +79,14 @@ class FileRules extends ValidationFileRules
|
|||
return true;
|
||||
}
|
||||
|
||||
// Get uploaded image size
|
||||
$info = getimagesize($file->getTempName());
|
||||
$fileWidth = $info[0];
|
||||
$fileHeight = $info[1];
|
||||
// Get Parameter sizes
|
||||
$x = $params[0] ?? 1;
|
||||
$y = $params[1] ?? 1;
|
||||
|
||||
if ($fileWidth !== $fileHeight) {
|
||||
// Get uploaded image size
|
||||
[0 => $fileWidth, 1 => $fileHeight] = getimagesize($file->getTempName());
|
||||
|
||||
if (($x / $y) !== ($fileWidth / $fileHeight)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ class Input extends FormComponent
|
|||
|
||||
public function render(): string
|
||||
{
|
||||
$baseClass = 'bg-white border-black rounded-lg focus:border-black border-3 focus:ring-castopod focus-within:ring-castopod ' . $this->class;
|
||||
$baseClass = 'w-full bg-white border-black rounded-lg focus:border-black border-3 focus:ring-castopod focus-within:ring-castopod ' . $this->class;
|
||||
|
||||
$this->attributes['class'] = $baseClass;
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ class MarkdownEditor extends FormComponent
|
|||
$this->attributes['class'] = 'border-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, html_entity_decode($this->value), null));
|
||||
$textarea = form_textarea($this->attributes, old($this->name, html_entity_decode($this->value), false));
|
||||
$icons = [
|
||||
'heading' => icon('heading'),
|
||||
'bold' => icon('bold'),
|
||||
|
|
|
@ -17,7 +17,7 @@ class Textarea extends FormComponent
|
|||
{
|
||||
unset($this->attributes['value']);
|
||||
|
||||
$this->attributes['class'] = 'focus:border-black focus:ring-castopod rounded-lg border-3 border-black ' . $this->class;
|
||||
$this->attributes['class'] = 'w-full focus:border-black focus:ring-castopod rounded-lg border-3 border-black ' . $this->class;
|
||||
|
||||
$textarea = form_textarea(
|
||||
$this->attributes,
|
||||
|
|
|
@ -36,7 +36,7 @@ class Toggler extends FormComponent
|
|||
|
||||
$this->attributes['class'] = 'form-switch';
|
||||
|
||||
$checkbox = form_checkbox($this->attributes, $this->value, old($this->name, $this->checked));
|
||||
$checkbox = form_checkbox($this->attributes, $this->value, old($this->name) === 'yes' ? true : $this->checked);
|
||||
$hint = $this->hint === '' ? '' : hint_tooltip($this->hint, 'ml-1');
|
||||
return <<<HTML
|
||||
<label class="relative inline-flex items-center {$wrapperClass}">
|
||||
|
|
|
@ -98,6 +98,10 @@ $routes->group(
|
|||
$routes->post('edit', 'PodcastController::attemptEdit/$1', [
|
||||
'filter' => 'permission:podcast-edit',
|
||||
]);
|
||||
$routes->get('edit/delete-banner', 'PodcastController::deleteBanner/$1', [
|
||||
'as' => 'podcast-banner-delete',
|
||||
'filter' => 'permission:podcast-edit',
|
||||
]);
|
||||
$routes->get('delete', 'PodcastController::delete/$1', [
|
||||
'as' => 'podcast-delete',
|
||||
'filter' => 'permission:podcasts-delete',
|
||||
|
|
|
@ -112,8 +112,8 @@ class EpisodeController extends BaseController
|
|||
{
|
||||
$rules = [
|
||||
'audio_file' => 'uploaded[audio_file]|ext_in[audio_file,mp3,m4a]',
|
||||
'image' =>
|
||||
'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]',
|
||||
'cover' =>
|
||||
'is_image[cover]|ext_in[cover,jpg,png]|min_dims[cover,1400,1400]|is_image_ratio[cover,1,1]',
|
||||
'transcript_file' =>
|
||||
'ext_in[transcript,txt,html,srt,json]|permit_empty',
|
||||
'chapters_file' => 'ext_in[chapters,json]|permit_empty',
|
||||
|
@ -126,12 +126,6 @@ class EpisodeController extends BaseController
|
|||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
$image = null;
|
||||
$imageFile = $this->request->getFile('image');
|
||||
if ($imageFile !== null && $imageFile->isValid()) {
|
||||
$image = new Image($imageFile);
|
||||
}
|
||||
|
||||
$newEpisode = new Episode([
|
||||
'podcast_id' => $this->podcast->id,
|
||||
'title' => $this->request->getPost('title'),
|
||||
|
@ -139,7 +133,6 @@ class EpisodeController extends BaseController
|
|||
'guid' => null,
|
||||
'audio_file' => $this->request->getFile('audio_file'),
|
||||
'description_markdown' => $this->request->getPost('description'),
|
||||
'image' => $image,
|
||||
'location' => $this->request->getPost('location_name') === '' ? null : new Location($this->request->getPost(
|
||||
'location_name'
|
||||
)),
|
||||
|
@ -163,6 +156,11 @@ class EpisodeController extends BaseController
|
|||
'published_at' => null,
|
||||
]);
|
||||
|
||||
$coverFile = $this->request->getFile('cover');
|
||||
if ($coverFile !== null && $coverFile->isValid()) {
|
||||
$newEpisode->cover = new Image($coverFile);
|
||||
}
|
||||
|
||||
$transcriptChoice = $this->request->getPost('transcript-choice');
|
||||
if (
|
||||
$transcriptChoice === 'upload-file'
|
||||
|
@ -238,8 +236,8 @@ class EpisodeController extends BaseController
|
|||
$rules = [
|
||||
'audio_file' =>
|
||||
'uploaded[audio_file]|ext_in[audio_file,mp3,m4a]|permit_empty',
|
||||
'image' =>
|
||||
'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]',
|
||||
'cover' =>
|
||||
'is_image[cover]|ext_in[cover,jpg,png]|min_dims[cover,1400,1400]|is_image_ratio[cover,1,1]',
|
||||
'transcript_file' =>
|
||||
'ext_in[transcript_file,txt,html,srt,json]|permit_empty',
|
||||
'chapters_file' => 'ext_in[chapters_file,json]|permit_empty',
|
||||
|
@ -279,9 +277,9 @@ class EpisodeController extends BaseController
|
|||
$this->episode->audio_file = $audioFile;
|
||||
}
|
||||
|
||||
$imageFile = $this->request->getFile('image');
|
||||
if ($imageFile !== null && $imageFile->isValid()) {
|
||||
$this->episode->image = new Image($imageFile);
|
||||
$coverFile = $this->request->getFile('cover');
|
||||
if ($coverFile !== null && $coverFile->isValid()) {
|
||||
$this->episode->cover = new Image($coverFile);
|
||||
}
|
||||
|
||||
$transcriptChoice = $this->request->getPost('transcript-choice');
|
||||
|
|
|
@ -66,8 +66,8 @@ class PersonController extends BaseController
|
|||
public function attemptCreate(): RedirectResponse
|
||||
{
|
||||
$rules = [
|
||||
'image' =>
|
||||
'is_image[image]|ext_in[image,jpg,jpeg,png]|min_dims[image,400,400]|is_image_squared[image]',
|
||||
'avatar' =>
|
||||
'is_image[avatar]|ext_in[avatar,jpg,jpeg,png]|min_dims[avatar,400,400]|is_image_ratio[avatar,1,1]',
|
||||
];
|
||||
|
||||
if (! $this->validate($rules)) {
|
||||
|
@ -85,9 +85,9 @@ class PersonController extends BaseController
|
|||
'updated_by' => user_id(),
|
||||
]);
|
||||
|
||||
$imageFile = $this->request->getFile('image');
|
||||
if ($imageFile !== null && $imageFile->isValid()) {
|
||||
$person->image = new Image($imageFile);
|
||||
$avatarFile = $this->request->getFile('avatar');
|
||||
if ($avatarFile !== null && $avatarFile->isValid()) {
|
||||
$person->avatar = new Image($avatarFile);
|
||||
}
|
||||
|
||||
$personModel = new PersonModel();
|
||||
|
@ -119,8 +119,8 @@ class PersonController extends BaseController
|
|||
public function attemptEdit(): RedirectResponse
|
||||
{
|
||||
$rules = [
|
||||
'image' =>
|
||||
'is_image[image]|ext_in[image,jpg,jpeg,png]|min_dims[image,400,400]|is_image_squared[image]',
|
||||
'avatar' =>
|
||||
'is_image[avatar]|ext_in[avatar,jpg,jpeg,png]|min_dims[avatar,400,400]|is_image_ratio[avatar,1,1]',
|
||||
];
|
||||
|
||||
if (! $this->validate($rules)) {
|
||||
|
@ -134,9 +134,9 @@ class PersonController extends BaseController
|
|||
$this->person->unique_name = $this->request->getPost('unique_name');
|
||||
$this->person->information_url = $this->request->getPost('information_url');
|
||||
|
||||
$imageFile = $this->request->getFile('image');
|
||||
if ($imageFile !== null && $imageFile->isValid()) {
|
||||
$this->person->image = new Image($imageFile);
|
||||
$avatarFile = $this->request->getFile('avatar');
|
||||
if ($avatarFile !== null && $avatarFile->isValid()) {
|
||||
$this->person->avatar = new Image($avatarFile);
|
||||
}
|
||||
|
||||
$this->person->updated_by = user_id();
|
||||
|
|
|
@ -171,8 +171,9 @@ class PodcastController extends BaseController
|
|||
public function attemptCreate(): RedirectResponse
|
||||
{
|
||||
$rules = [
|
||||
'image' =>
|
||||
'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]',
|
||||
'cover' =>
|
||||
'uploaded[cover]|is_image[cover]|ext_in[cover,jpg,png]|min_dims[cover,1400,1400]|is_image_ratio[cover,1,1]',
|
||||
'banner' => 'is_image[banner]|ext_in[banner,jpg,png]|min_dims[banner,1500,500]|is_image_ratio[banner,3,1]',
|
||||
];
|
||||
|
||||
if (! $this->validate($rules)) {
|
||||
|
@ -195,7 +196,7 @@ class PodcastController extends BaseController
|
|||
'title' => $this->request->getPost('title'),
|
||||
'handle' => $this->request->getPost('handle'),
|
||||
'description_markdown' => $this->request->getPost('description'),
|
||||
'image' => new Image($this->request->getFile('image')),
|
||||
'cover' => new Image($this->request->getFile('cover')),
|
||||
'language_code' => $this->request->getPost('language'),
|
||||
'category_id' => $this->request->getPost('category'),
|
||||
'parental_advisory' =>
|
||||
|
@ -224,6 +225,11 @@ class PodcastController extends BaseController
|
|||
'updated_by' => user_id(),
|
||||
]);
|
||||
|
||||
$bannerFile = $this->request->getFile('banner');
|
||||
if ($bannerFile !== null && $bannerFile->isValid()) {
|
||||
$podcast->banner = new Image($bannerFile);
|
||||
}
|
||||
|
||||
$podcastModel = new PodcastModel();
|
||||
$db = db_connect();
|
||||
|
||||
|
@ -279,8 +285,9 @@ class PodcastController extends BaseController
|
|||
public function attemptEdit(): RedirectResponse
|
||||
{
|
||||
$rules = [
|
||||
'image' =>
|
||||
'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]',
|
||||
'cover' =>
|
||||
'is_image[cover]|ext_in[cover,jpg,png]|min_dims[cover,1400,1400]|is_image_ratio[cover,1,1]',
|
||||
'banner' => 'is_image[banner]|ext_in[banner,jpg,png]|min_dims[banner,1500,500]|is_image_ratio[banner,3,1]',
|
||||
];
|
||||
|
||||
if (! $this->validate($rules)) {
|
||||
|
@ -302,9 +309,13 @@ class PodcastController extends BaseController
|
|||
$this->podcast->title = $this->request->getPost('title');
|
||||
$this->podcast->description_markdown = $this->request->getPost('description');
|
||||
|
||||
$image = $this->request->getFile('image');
|
||||
if ($image !== null && $image->isValid()) {
|
||||
$this->podcast->image = new Image($image);
|
||||
$coverFile = $this->request->getFile('cover');
|
||||
if ($coverFile !== null && $coverFile->isValid()) {
|
||||
$this->podcast->cover = new Image($coverFile);
|
||||
}
|
||||
$bannerFile = $this->request->getFile('banner');
|
||||
if ($bannerFile !== null && $bannerFile->isValid()) {
|
||||
$this->podcast->banner = new Image($bannerFile);
|
||||
}
|
||||
$this->podcast->language_code = $this->request->getPost('language');
|
||||
$this->podcast->category_id = $this->request->getPost('category');
|
||||
|
@ -356,6 +367,28 @@ class PodcastController extends BaseController
|
|||
return redirect()->route('podcast-view', [$this->podcast->id]);
|
||||
}
|
||||
|
||||
public function deleteBanner(): RedirectResponse
|
||||
{
|
||||
if ($this->podcast->banner === null) {
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
$this->podcast->banner->delete(config('Images')->podcastBannerSizes);
|
||||
|
||||
$this->podcast->banner_path = null;
|
||||
$this->podcast->banner_mimetype = null;
|
||||
|
||||
$podcastModel = new PodcastModel();
|
||||
if (! $podcastModel->update($this->podcast->id, $this->podcast)) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $podcastModel->errors());
|
||||
}
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function latestEpisodes(int $limit, int $podcastId): string
|
||||
{
|
||||
$episodes = (new EpisodeModel())
|
||||
|
|
|
@ -115,9 +115,9 @@ class PodcastImportController extends BaseController
|
|||
property_exists($nsItunes, 'image') && $nsItunes->image !== null &&
|
||||
$nsItunes->image->attributes()['href'] !== null
|
||||
) {
|
||||
$imageFile = download_file((string) $nsItunes->image->attributes()['href']);
|
||||
$coverFile = download_file((string) $nsItunes->image->attributes()['href']);
|
||||
} else {
|
||||
$imageFile = download_file((string) $feed->channel[0]->image->url);
|
||||
$coverFile = download_file((string) $feed->channel[0]->image->url);
|
||||
}
|
||||
|
||||
$location = null;
|
||||
|
@ -140,7 +140,8 @@ class PodcastImportController extends BaseController
|
|||
'title' => (string) $feed->channel[0]->title,
|
||||
'description_markdown' => $converter->convert($channelDescriptionHtml),
|
||||
'description_html' => $channelDescriptionHtml,
|
||||
'image' => new Image($imageFile),
|
||||
'cover' => new Image($coverFile),
|
||||
'banner' => null,
|
||||
'language_code' => $this->request->getPost('language'),
|
||||
'category_id' => $this->request->getPost('category'),
|
||||
'parental_advisory' =>
|
||||
|
@ -249,7 +250,7 @@ class PodcastImportController extends BaseController
|
|||
'full_name' => $fullName,
|
||||
'unique_name' => slugify($fullName),
|
||||
'information_url' => $podcastPerson->attributes()['href'],
|
||||
'image' => new Image(download_file((string) $podcastPerson->attributes()['img'])),
|
||||
'avatar' => new Image(download_file((string) $podcastPerson->attributes()['img'])),
|
||||
'created_by' => user_id(),
|
||||
'updated_by' => user_id(),
|
||||
]);
|
||||
|
@ -326,9 +327,9 @@ class PodcastImportController extends BaseController
|
|||
property_exists($nsItunes, 'image') && $nsItunes->image !== null &&
|
||||
$nsItunes->image->attributes()['href'] !== null
|
||||
) {
|
||||
$episodeImage = new Image(download_file((string) $nsItunes->image->attributes()['href']));
|
||||
$episodeCover = new Image(download_file((string) $nsItunes->image->attributes()['href']));
|
||||
} else {
|
||||
$episodeImage = null;
|
||||
$episodeCover = null;
|
||||
}
|
||||
|
||||
$location = null;
|
||||
|
@ -351,7 +352,7 @@ class PodcastImportController extends BaseController
|
|||
),
|
||||
'description_markdown' => $converter->convert($itemDescriptionHtml),
|
||||
'description_html' => $itemDescriptionHtml,
|
||||
'image' => $episodeImage,
|
||||
'cover' => $episodeCover,
|
||||
'parental_advisory' =>
|
||||
property_exists($nsItunes, 'explicit') && $nsItunes->explicit !== null
|
||||
? (in_array((string) $nsItunes->explicit, ['yes', 'true'], true)
|
||||
|
@ -402,7 +403,7 @@ class PodcastImportController extends BaseController
|
|||
'full_name' => $fullName,
|
||||
'unique_name' => slugify($fullName),
|
||||
'information_url' => $episodePerson->attributes()['href'],
|
||||
'image' => new Image(download_file((string) $episodePerson->attributes()['img'])),
|
||||
'avatar' => new Image(download_file((string) $episodePerson->attributes()['img'])),
|
||||
'created_by' => user_id(),
|
||||
'updated_by' => user_id(),
|
||||
]);
|
||||
|
|
|
@ -25,7 +25,7 @@ class SettingsController extends BaseController
|
|||
{
|
||||
$rules = [
|
||||
'site_icon' =>
|
||||
'is_image[site_icon]|ext_in[site_icon,png,jpeg]|is_image_squared[site_icon]|min_dims[image,512,512]|permit_empty',
|
||||
'is_image[site_icon]|ext_in[site_icon,png,jpeg]|is_image_ratio[site_icon,1,1]|min_dims[image,512,512]|permit_empty',
|
||||
];
|
||||
|
||||
if (! $this->validate($rules)) {
|
||||
|
|
|
@ -38,8 +38,6 @@ return [
|
|||
'noChoicesText' => 'No choices to choose from',
|
||||
'maxItemText' => 'Cannot add more items',
|
||||
],
|
||||
'image_size_hint' =>
|
||||
'Image must be squared with at least 1400px wide and tall.',
|
||||
'upload_file' => 'Upload a file',
|
||||
'remote_url' => 'Remote URL',
|
||||
],
|
||||
|
|
|
@ -49,10 +49,10 @@ return [
|
|||
'audio_file' => 'Audio file',
|
||||
'audio_file_hint' => 'Choose an .mp3 or .m4a audio file.',
|
||||
'info_section_title' => 'Episode info',
|
||||
'info_section_subtitle' => '',
|
||||
'image' => 'Cover image',
|
||||
'image_hint' =>
|
||||
'If you do not set an image, the podcast cover will be used instead.',
|
||||
'cover' => 'Episode cover',
|
||||
'cover_hint' =>
|
||||
'If you do not set a cover, the podcast cover will be used instead.',
|
||||
'cover_size_hint' => 'Cover must be squared with at least 1400px wide and tall.',
|
||||
'title' => 'Title',
|
||||
'title_hint' =>
|
||||
'Should contain a clear and concise episode name. Do not specify the episode or season numbers here.',
|
||||
|
|
|
@ -17,9 +17,9 @@ return [
|
|||
'edit' => 'Edit person',
|
||||
'delete' => 'Delete person',
|
||||
'form' => [
|
||||
'image' => 'Picture',
|
||||
'image_size_hint' =>
|
||||
'Image must be squared with at least 400px wide and tall.',
|
||||
'avatar' => 'Avatar',
|
||||
'avatar_size_hint' =>
|
||||
'Avatar must be squared with at least 400px wide and tall.',
|
||||
'full_name' => 'Full name',
|
||||
'full_name_hint' => 'This is the full name or alias of the person.',
|
||||
'unique_name' => 'Unique name',
|
||||
|
|
|
@ -25,7 +25,11 @@ return [
|
|||
'form' => [
|
||||
'identity_section_title' => 'Podcast identity',
|
||||
'identity_section_subtitle' => 'These fields allow you to get noticed.',
|
||||
'image' => 'Cover image',
|
||||
'cover' => 'Podcast cover',
|
||||
'cover_size_hint' => 'Cover must be squared with at least 1400px wide and tall.',
|
||||
'banner' => 'Podcast banner',
|
||||
'banner_size_hint' => 'Banner must have a 3:1 ratio with at least 1500px wide.',
|
||||
'banner_delete' => 'Delete podcast banner',
|
||||
'title' => 'Title',
|
||||
'handle' => 'Handle',
|
||||
'handle_hint' =>
|
||||
|
|
|
@ -11,8 +11,8 @@ declare(strict_types=1);
|
|||
return [
|
||||
'min_dims' =>
|
||||
'{field} is either not an image, or it is not wide or tall enough.',
|
||||
'is_image_squared' =>
|
||||
'{field} is either not an image, or it is not squared (width and height differ).',
|
||||
'is_image_ratio' =>
|
||||
'{field} is either not an image or not of the right ratio.',
|
||||
'validate_url' =>
|
||||
'The {field} field must be a valid URL (eg. https://example.com/).',
|
||||
];
|
||||
|
|
|
@ -38,8 +38,6 @@ return [
|
|||
'noChoicesText' => 'Aucune sélection possible',
|
||||
'maxItemText' => 'Impossible de rajouter un élément',
|
||||
],
|
||||
'image_size_hint' =>
|
||||
'L’image doit être carrée, avec au minimum 1400px de long et de large.',
|
||||
'upload_file' => 'Téléversez un fichier',
|
||||
'remote_url' => 'URL distante',
|
||||
],
|
||||
|
|
|
@ -50,10 +50,10 @@ return [
|
|||
'audio_file' => 'Fichier audio',
|
||||
'audio_file_hint' => 'Sélectionnez un fichier audio .mp3 ou .m4a.',
|
||||
'info_section_title' => 'Informations épisode',
|
||||
'info_section_subtitle' => '',
|
||||
'image' => 'Image de couverture',
|
||||
'image_hint' =>
|
||||
'cover' => 'Image de couverture',
|
||||
'cover_hint' =>
|
||||
'Si vous ne définissez pas d’image, celle du podcast sera utilisée à la place.',
|
||||
'cover_size_hint' => 'La couverture de l’épisode doit être carrée, avec au minimum 1400px de largeur et de hauteur.',
|
||||
'title' => 'Titre',
|
||||
'title_hint' =>
|
||||
'Doit contenir un titre d’épisode clair et concis. Ne précisez ici aucun numéro de saison ou d’épisode.',
|
||||
|
|
|
@ -17,8 +17,8 @@ return [
|
|||
'edit' => 'Modifier l’intervenant',
|
||||
'delete' => 'Supprimer l’intervenant',
|
||||
'form' => [
|
||||
'image' => 'Photo',
|
||||
'image_size_hint' =>
|
||||
'avatar' => 'Avatar',
|
||||
'avatar_size_hint' =>
|
||||
'L’image doit être carrée et avoir au moins 400px de largeur et de hauteur.',
|
||||
'full_name' => 'Nom complet',
|
||||
'full_name_hint' => 'Le nom complet ou le pseudonyme de l’intervenant',
|
||||
|
|
|
@ -26,7 +26,11 @@ return [
|
|||
'identity_section_title' => 'Informations sur le Podcast',
|
||||
'identity_section_subtitle' =>
|
||||
'Ces champs vous permettent de vous faire remarquer.',
|
||||
'image' => 'Image de couverture',
|
||||
'cover' => 'Couverture du podcast',
|
||||
'cover_size_hint' => 'La couverture du podcast doit être carrée, avec au minimum 1400px de largeur et de hauteur.',
|
||||
'banner' => 'Bannière du podcast',
|
||||
'banner_size_hint' => 'La bannière doit être au format 3/1, avec au minimum 1500px de largeur.',
|
||||
'banner_delete' => 'Supprimer la bannière du podcast',
|
||||
'title' => 'Titre',
|
||||
'handle' => 'Identifiant',
|
||||
'handle_hint' =>
|
||||
|
|
|
@ -16,7 +16,7 @@ return [
|
|||
'site_icon' => 'Favicon du site',
|
||||
'site_icon_delete' => 'Supprimer la favicon du site',
|
||||
'site_icon_hint' => 'Les favicons sont ce que vous voyez sur les onglets de votre navigateur, dans votre barre de favoris, et lorsque vous ajoutez un site web en raccourci sur des appareils mobiles.',
|
||||
'site_icon_helper' => 'La favicon doit être carrée et avoir au moins 512px de largeur et de hauteur.',
|
||||
'site_icon_helper' => 'La favicon doit être carrée, avec au minimum 512px de largeur et de hauteur.',
|
||||
'site_name' => 'Titre du site',
|
||||
'site_description' => 'Description du site',
|
||||
'submit' => 'Save',
|
||||
|
|
|
@ -11,8 +11,8 @@ declare(strict_types=1);
|
|||
return [
|
||||
'min_dims' =>
|
||||
'{field} n’est pas une image ou n’a pas la taille minimale requise.',
|
||||
'is_image_squared' =>
|
||||
'{field} n’est pas une image ou n’est pas carré (largeur et hauteur différentes).',
|
||||
'is_image_ratio' =>
|
||||
'{field} n’est pas une image ou n’est pas au bon format.',
|
||||
'validate_url' =>
|
||||
'Le champs {field} doit être une adresse valide (par exemple https://exemple.com/).',
|
||||
];
|
||||
|
|
|
@ -28,6 +28,7 @@ parameters:
|
|||
- '#Function \"preg_.*\(\)\" cannot be used/left in the code#'
|
||||
- '#Function "property_exists\(\)" cannot be used/left in the code#'
|
||||
- '#Instead of "instanceof/is_a\(\)" use ReflectionProvider service or "\(new ObjectType\(<desired_type\>\)\)\-\>isSuperTypeOf\(<element_type\>\)" for static reflection to work#'
|
||||
- '#^Access to an undefined property App\\Entities\\Image#'
|
||||
-
|
||||
message: '#Function "function_exists\(\)" cannot be used/left in the code#'
|
||||
paths:
|
||||
|
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
|
@ -36,7 +36,7 @@
|
|||
|
||||
$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>
|
||||
<span class="inline-flex items-center flex-1 text-sm"><img src="{$userPodcast->cover->tiny_url}" class="w-6 h-6 mr-2 rounded-full" />{$userPodcast->title}{$checkMark}</span>
|
||||
</button>
|
||||
CODE_SAMPLE;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<a href="<?= route_to('episode-view', $episode->podcast->id, $episode->id) ?>" class="flex flex-col justify-end w-full h-full text-white group">
|
||||
<div class="absolute bottom-0 left-0 z-10 w-full h-full backdrop-gradient"></div>
|
||||
<div class="w-full h-full overflow-hidden">
|
||||
<img src="<?= $episode->image->medium_url ?>" alt="<?= $episode->title ?>" class="object-cover w-full h-full transition duration-200 ease-in-out transform group-focus:scale-105 group-hover:scale-105" />
|
||||
<img src="<?= $episode->cover->medium_url ?>" alt="<?= $episode->title ?>" class="object-cover w-full h-full transition duration-200 ease-in-out transform group-focus:scale-105 group-hover:scale-105" />
|
||||
</div>
|
||||
<?= publication_pill($episode->published_at, $episode->publication_status, 'absolute top-0 left-0 ml-2 mt-2 text-sm'); ?>
|
||||
<div class="absolute z-20 flex flex-col items-start px-4 py-2">
|
||||
|
|
|
@ -10,7 +10,7 @@ $podcastNavigation = [
|
|||
<a href="<?= route_to('podcast-view', $podcast->id) ?>" class="flex items-center px-4 py-2 border-b border-pine-900 focus:ring-inset focus:ring-castopod">
|
||||
<?= icon('arrow-left', 'mr-2') ?>
|
||||
<img
|
||||
src="<?= $podcast->image->thumbnail_url ?>"
|
||||
src="<?= $podcast->cover->tiny_url ?>"
|
||||
alt="<?= $podcast->title ?>"
|
||||
class="object-cover w-6 h-6 rounded"
|
||||
/>
|
||||
|
@ -18,7 +18,7 @@ $podcastNavigation = [
|
|||
</a>
|
||||
<div class="flex items-center px-4 py-2 border-b border-pine-900">
|
||||
<img
|
||||
src="<?= $episode->image->thumbnail_url ?>"
|
||||
src="<?= $episode->cover->thumbnail_url ?>"
|
||||
alt="<?= $episode->title ?>"
|
||||
class="object-cover w-16 h-16 rounded"
|
||||
/>
|
||||
|
|
|
@ -28,10 +28,10 @@
|
|||
required="true" />
|
||||
|
||||
<Forms.Field
|
||||
name="image"
|
||||
label="<?= lang('Episode.form.image') ?>"
|
||||
hint="<?= lang('Episode.form.image_hint') ?>"
|
||||
helper="<?= lang('Common.forms.image_size_hint', ) ?>"
|
||||
name="cover"
|
||||
label="<?= lang('Episode.form.cover') ?>"
|
||||
hint="<?= lang('Episode.form.cover_hint') ?>"
|
||||
helper="<?= lang('Episode.form.cover_size_hint', ) ?>"
|
||||
type="file"
|
||||
accept=".jpg,.jpeg,.png" />
|
||||
|
||||
|
|
|
@ -31,10 +31,10 @@
|
|||
accept=".mp3,.m4a" />
|
||||
|
||||
<Forms.Field
|
||||
name="image"
|
||||
label="<?= lang('Episode.form.image') ?>"
|
||||
hint="<?= lang('Episode.form.image_hint') ?>"
|
||||
helper="<?= lang('Common.forms.image_size_hint', ) ?>"
|
||||
name="cover"
|
||||
label="<?= lang('Episode.form.cover') ?>"
|
||||
hint="<?= lang('Episode.form.cover_hint') ?>"
|
||||
helper="<?= lang('Episode.form.cover_size_hint', ) ?>"
|
||||
type="file"
|
||||
accept=".jpg,.jpeg,.png" />
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
$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" />' .
|
||||
'<img loading="lazy" src="' . $episode->cover->thumbnail_url . '" alt="' . $episode->title . '" class="object-cover w-20 h-20 rounded-lg" />' .
|
||||
'</div>' .
|
||||
'<a class="overflow-x-hidden text-sm hover:underline" href="' . route_to(
|
||||
'episode-view',
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
return '<div class="flex">' .
|
||||
'<a href="' .
|
||||
route_to('person-view', $person->id) .
|
||||
"\"><img src=\"{$person->image->thumbnail_url}\" alt=\"{$person->full_name}\" class=\"object-cover w-16 h-16 rounded-full\" /></a>" .
|
||||
"\"><img src=\"{$person->avatar->thumbnail_url}\" alt=\"{$person->full_name}\" class=\"object-cover w-16 h-16 rounded-full\" /></a>" .
|
||||
'<div class="flex flex-col ml-3">' .
|
||||
$person->full_name .
|
||||
implode(
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
<Forms.Textarea name="message" placeholder="<?= lang('Episode.publish_form.message_placeholder') ?>" autofocus="" rows="2" />
|
||||
</div>
|
||||
<div class="flex border-t border-b">
|
||||
<img src="<?= $episode->image
|
||||
<img src="<?= $episode->cover
|
||||
->thumbnail_url ?>" alt="<?= $episode->title ?>" class="w-24 h-24" />
|
||||
<div class="flex flex-col flex-1">
|
||||
<a href="<?= $episode->link ?>" class="flex-1 px-4 py-2">
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<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">
|
||||
<img src="<?= $episode->image
|
||||
<img src="<?= $episode->cover
|
||||
->thumbnail_url ?>" alt="<?= $episode->title ?>" class="w-24 h-24" />
|
||||
<div class="flex flex-col flex-1">
|
||||
<a href="<?= $episode->link ?>" class="flex-1 px-4 py-2">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<a href="<?= route_to('person-view', $person->id) ?>" class="flex flex-col justify-end w-full h-full text-white group">
|
||||
<div class="absolute bottom-0 left-0 z-10 w-full h-full backdrop-gradient"></div>
|
||||
<div class="w-full h-full overflow-hidden">
|
||||
<img alt="<?= $person->full_name ?>" src="<?= $person->image->medium_url ?>" class="object-cover w-full h-full transition duration-200 ease-in-out transform group-focus:scale-105 group-hover:scale-105" />
|
||||
<img alt="<?= $person->full_name ?>" src="<?= $person->avatar->medium_url ?>" class="object-cover w-full h-full transition duration-200 ease-in-out transform group-focus:scale-105 group-hover:scale-105" />
|
||||
</div>
|
||||
<div class="absolute z-20">
|
||||
<h2 class="px-4 py-2 font-semibold leading-tight"><?= $person->full_name ?></h2>
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
<?= csrf_field() ?>
|
||||
|
||||
<Forms.Field
|
||||
name="image"
|
||||
label="<?= lang('Person.form.image') ?>"
|
||||
helper="<?= lang('Person.form.image_size_hint') ?>"
|
||||
name="avatar"
|
||||
label="<?= lang('Person.form.avatar') ?>"
|
||||
helper="<?= lang('Person.form.avatar_size_hint') ?>"
|
||||
type="file"
|
||||
accept=".jpg,.jpeg,.png" />
|
||||
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
<?= csrf_field() ?>
|
||||
|
||||
<Forms.Field
|
||||
name="image"
|
||||
label="<?= lang('Person.form.image') ?>"
|
||||
helper="<?= lang('Person.form.image_size_hint') ?>"
|
||||
name="avatar"
|
||||
label="<?= lang('Person.form.avatar') ?>"
|
||||
helper="<?= lang('Person.form.avatar_size_hint') ?>"
|
||||
type="file"
|
||||
accept=".jpg,.jpeg,.png" />
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<div class="flex flex-wrap">
|
||||
<div class="w-full max-w-sm mb-6 md:mr-4">
|
||||
<img
|
||||
src="<?= $person->image->medium_url ?>"
|
||||
src="<?= $person->avatar->medium_url ?>"
|
||||
alt="$person->full_name"
|
||||
class="object-cover w-full rounded"
|
||||
/>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class="w-full h-full overflow-hidden">
|
||||
<img
|
||||
alt="<?= $podcast->title ?>"
|
||||
src="<?= $podcast->image->medium_url ?>" class="object-cover w-full h-full transition duration-200 ease-in-out transform group-focus:scale-105 group-hover:scale-105" />
|
||||
src="<?= $podcast->cover->medium_url ?>" class="object-cover w-full h-full transition duration-200 ease-in-out transform group-focus:scale-105 group-hover:scale-105" />
|
||||
</div>
|
||||
<div class="absolute z-20 px-4 pb-4 transition duration-75 ease-out translate-y-6 group-focus:translate-y-0 group-hover:translate-y-0">
|
||||
<h2 class="font-bold leading-none truncate font-display"><?= $podcast->title ?></h2>
|
||||
|
|
|
@ -37,7 +37,7 @@ $podcastNavigation = [
|
|||
|
||||
<div class="flex items-center px-4 py-2 border-b border-pine-900">
|
||||
<img
|
||||
src="<?= $podcast->image->thumbnail_url ?>"
|
||||
src="<?= $podcast->cover->thumbnail_url ?>"
|
||||
alt="<?= $podcast->title ?>"
|
||||
class="object-cover w-16 h-16 rounded"
|
||||
/>
|
||||
|
|
|
@ -14,22 +14,28 @@
|
|||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<form action="<?= route_to('podcast-create') ?>" method="POST" enctype='multipart/form-data' class="flex flex-col">
|
||||
<form action="<?= route_to('podcast-create') ?>" method="POST" enctype='multipart/form-data' class="flex flex-col gap-y-6">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<Forms.Section
|
||||
class="mb-8"
|
||||
title="<?= lang('Podcast.form.identity_section_title') ?>"
|
||||
subtitle="<?= lang('Podcast.form.identity_section_subtitle') ?>" >
|
||||
|
||||
<Forms.Field
|
||||
name="image"
|
||||
label="<?= lang('Podcast.form.image') ?>"
|
||||
helper="<?= lang('Common.forms.image_size_hint') ?>"
|
||||
name="cover"
|
||||
label="<?= lang('Podcast.form.cover') ?>"
|
||||
helper="<?= lang('Podcast.form.cover_size_hint') ?>"
|
||||
type="file"
|
||||
required="true"
|
||||
accept=".jpg,.jpeg,.png" />
|
||||
|
||||
<Forms.Field
|
||||
name="banner"
|
||||
label="<?= lang('Podcast.form.banner') ?>"
|
||||
helper="<?= lang('Podcast.form.banner_size_hint') ?>"
|
||||
type="file"
|
||||
accept=".jpg,.jpeg,.png" />
|
||||
|
||||
<Forms.Field
|
||||
name="title"
|
||||
label="<?= lang('Podcast.form.title') ?>"
|
||||
|
@ -67,7 +73,6 @@
|
|||
</Forms.Section>
|
||||
|
||||
<Forms.Section
|
||||
class="mb-8"
|
||||
title="<?= lang('Podcast.form.classification_section_title') ?>"
|
||||
subtitle="<?= lang('Podcast.form.classification_section_subtitle') ?>" >
|
||||
|
||||
|
@ -114,7 +119,6 @@
|
|||
</Forms.Section>
|
||||
|
||||
<Forms.Section
|
||||
class="mb-8"
|
||||
title="<?= lang('Podcast.form.author_section_title') ?>"
|
||||
subtitle="<?= lang('Podcast.form.author_section_subtitle') ?>" >
|
||||
|
||||
|
@ -143,7 +147,6 @@
|
|||
</Forms.Section>
|
||||
|
||||
<Forms.Section
|
||||
class="mb-8"
|
||||
title="<?= lang('Podcast.form.location_section_title') ?>"
|
||||
subtitle="<?= lang('Podcast.form.location_section_subtitle') ?>" >
|
||||
|
||||
|
@ -155,7 +158,6 @@
|
|||
</Forms.Section>
|
||||
|
||||
<Forms.Section
|
||||
class="mb-8"
|
||||
title="<?= lang('Podcast.form.monetization_section_title') ?>"
|
||||
subtitle="<?= lang('Podcast.form.monetization_section_subtitle') ?>" >
|
||||
|
||||
|
@ -184,7 +186,6 @@
|
|||
</Forms.Section>
|
||||
|
||||
<Forms.Section
|
||||
class="mb-8"
|
||||
title="<?= lang('Podcast.form.advanced_section_title') ?>"
|
||||
subtitle="<?= lang('Podcast.form.advanced_section_subtitle') ?>" >
|
||||
|
||||
|
@ -197,7 +198,6 @@
|
|||
</Forms.Section>
|
||||
|
||||
<Forms.Section
|
||||
class="mb-8"
|
||||
title="<?= lang('Podcast.form.status_section_title') ?>" >
|
||||
<Forms.Toggler class="mb-2" name="lock" value="yes" checked="true" hint="<?= lang('Podcast.form.lock_hint') ?>">
|
||||
<?= lang('Podcast.form.lock') ?>
|
||||
|
|
|
@ -18,18 +18,41 @@
|
|||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<form id="podcast-edit-form" action="<?= route_to('podcast-edit', $podcast->id) ?>" method="POST" enctype='multipart/form-data' class="flex flex-col">
|
||||
<form id="podcast-edit-form" action="<?= route_to('podcast-edit', $podcast->id) ?>" method="POST" enctype='multipart/form-data' class="flex flex-row-reverse flex-wrap items-start justify-end gap-4">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="sticky z-40 flex flex-col w-full max-w-xs overflow-hidden bg-white shadow-sm border-3 border-pine-100 top-24 rounded-xl">
|
||||
<?php if ($podcast->banner_path !== null): ?>
|
||||
<a href="<?= route_to('podcast-banner-delete', $podcast->id) ?>" class="absolute p-1 text-white bg-red-600 border-2 border-black rounded-full hover:bg-red-800 focus:ring-castopod top-2 right-2" title="<?= lang('Podcast.form.banner_delete') ?>"><?= icon('delete-bin') ?></a>
|
||||
<?php endif; ?>
|
||||
<img src="<?= $podcast->banner->small_url ?>" alt="" class="object-cover w-full aspect-[3/1] bg-pine-800" />
|
||||
<div class="flex px-4 py-2">
|
||||
<img src="<?= $podcast->cover->thumbnail_url ?>" alt="<?= $podcast->title ?>"
|
||||
class="w-16 h-16 mr-4 -mt-8 rounded-full ring-2 ring-white" />
|
||||
<div class="flex flex-col">
|
||||
<p class="font-semibold leading-none"><?= $podcast->title ?></p>
|
||||
<p class="text-sm text-gray-500">@<?= $podcast->handle ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-y-6">
|
||||
|
||||
<Forms.Section
|
||||
class="mb-8"
|
||||
title="<?= lang('Podcast.form.identity_section_title') ?>"
|
||||
subtitle="<?= lang('Podcast.form.identity_section_subtitle') ?>" >
|
||||
|
||||
<Forms.Field
|
||||
name="image"
|
||||
label="<?= lang('Podcast.form.image') ?>"
|
||||
helper="<?= lang('Common.forms.image_size_hint') ?>"
|
||||
name="cover"
|
||||
label="<?= lang('Podcast.form.cover') ?>"
|
||||
helper="<?= lang('Podcast.form.cover_size_hint') ?>"
|
||||
type="file"
|
||||
accept=".jpg,.jpeg,.png" />
|
||||
|
||||
<Forms.Field
|
||||
name="banner"
|
||||
label="<?= lang('Podcast.form.banner') ?>"
|
||||
helper="<?= lang('Podcast.form.banner_size_hint') ?>"
|
||||
type="file"
|
||||
accept=".jpg,.jpeg,.png" />
|
||||
|
||||
|
@ -65,7 +88,6 @@
|
|||
</Forms.Section>
|
||||
|
||||
<Forms.Section
|
||||
class="mb-8"
|
||||
title="<?= lang('Podcast.form.classification_section_title') ?>"
|
||||
subtitle="<?= lang('Podcast.form.classification_section_subtitle') ?>" >
|
||||
|
||||
|
@ -114,7 +136,6 @@
|
|||
</Forms.Section>
|
||||
|
||||
<Forms.Section
|
||||
class="mb-8"
|
||||
title="<?= lang('Podcast.form.author_section_title') ?>"
|
||||
subtitle="<?= lang('Podcast.form.author_section_subtitle') ?>" >
|
||||
|
||||
|
@ -147,7 +168,6 @@
|
|||
</Forms.Section>
|
||||
|
||||
<Forms.Section
|
||||
class="mb-8"
|
||||
title="<?= lang('Podcast.form.location_section_title') ?>"
|
||||
subtitle="<?= lang('Podcast.form.location_section_subtitle') ?>" >
|
||||
|
||||
|
@ -160,7 +180,6 @@
|
|||
</Forms.Section>
|
||||
|
||||
<Forms.Section
|
||||
class="mb-8"
|
||||
title="<?= lang('Podcast.form.monetization_section_title') ?>"
|
||||
subtitle="<?= lang('Podcast.form.monetization_section_subtitle') ?>" >
|
||||
|
||||
|
@ -190,7 +209,6 @@
|
|||
</Forms.Section>
|
||||
|
||||
<Forms.Section
|
||||
class="mb-8"
|
||||
title="<?= lang('Podcast.form.advanced_section_title') ?>"
|
||||
subtitle="<?= lang('Podcast.form.advanced_section_subtitle') ?>" >
|
||||
|
||||
|
@ -204,7 +222,6 @@
|
|||
</Forms.Section>
|
||||
|
||||
<Forms.Section
|
||||
class="mb-8"
|
||||
title="<?= lang('Podcast.form.status_section_title') ?>" >
|
||||
<Forms.Toggler class="mb-2" name="lock" value="yes" checked="<?= $podcast->is_locked ? 'true' : 'false' ?>" hint="<?= lang('Podcast.form.lock_hint') ?>">
|
||||
<?= lang('Podcast.form.lock') ?>
|
||||
|
@ -217,6 +234,9 @@
|
|||
</Forms.Toggler>
|
||||
</Forms.Section>
|
||||
|
||||
<Button variant="primary" type="submit" class="self-end"><?= lang('Podcast.form.submit_edit') ?></Button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<?= $this->endSection() ?>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<section class="flex flex-col">
|
||||
<header class="flex justify-between py-2">
|
||||
<header class="flex justify-between">
|
||||
<Heading tagName="h2"><?= lang('Podcast.latest_episodes') ?></Heading>
|
||||
<a href="<?= route_to(
|
||||
'episode-list',
|
||||
|
@ -10,7 +10,7 @@
|
|||
</a>
|
||||
</header>
|
||||
<?php if ($episodes): ?>
|
||||
<div class="grid px-4 py-2 -mx-2 overflow-x-auto grid-cols-latestEpisodes gap-x-4 snap snap-x snap-proximity">
|
||||
<div class="grid px-4 pt-2 pb-5 -mx-2 overflow-x-auto grid-cols-latestEpisodes gap-x-4 snap snap-x snap-proximity">
|
||||
<?php foreach ($episodes as $episode): ?>
|
||||
<?= view('episode/_card', [
|
||||
'episode' => $episode,
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
return '<div class="flex">' .
|
||||
'<a href="' .
|
||||
route_to('person-view', $person->id) .
|
||||
"\"><img src=\"{$person->image->thumbnail_url}\" alt=\"{$person->full_name}\" class=\"object-cover w-16 h-16 rounded-full\" /></a>" .
|
||||
"\"><img src=\"{$person->avatar->thumbnail_url}\" alt=\"{$person->full_name}\" class=\"object-cover w-16 h-16 rounded-full\" /></a>" .
|
||||
'<div class="flex flex-col ml-3">' .
|
||||
$person->full_name .
|
||||
implode(
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<form action="<?= route_to('settings-instance') ?>" method="POST" class="flex flex-col max-w-sm gap-y-4" enctype="multipart/form-data">
|
||||
<form action="<?= route_to('settings-instance') ?>" method="POST" class="flex flex-col gap-y-4" enctype="multipart/form-data">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<Forms.Section
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
$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>
|
||||
<span class="inline-flex items-center flex-1 text-sm"><img src="{$userPodcast->cover->tiny_url}" class="w-6 h-6 mr-2 rounded-full" />{$userPodcast->title}{$checkMark}</span>
|
||||
</button>
|
||||
CODE_SAMPLE;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<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" />
|
||||
<img src="<?= $person->avatar->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">
|
||||
<?php if ($person->information_url): ?>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
</head>
|
||||
|
||||
<body class="flex" style="background: <?= $themeData['background'] ?>; color: <?= $themeData['text'] ?>;">
|
||||
<img src="<?= $episode->image->thumbnail_url ?>" alt="<?= $episode->title ?>" class="flex-shrink w-36 h-36" />
|
||||
<img src="<?= $episode->cover->thumbnail_url ?>" alt="<?= $episode->title ?>" class="flex-shrink w-36 h-36" />
|
||||
<div class="flex flex-col items-start flex-1 min-w-0 px-4 pt-4 h-36">
|
||||
<a href="https://castopod.org/" class="absolute top-0 right-0 mt-1 mr-2 text-2xl text-pine-500 hover:opacity-75" title="<?= lang('Common.powered_by', [
|
||||
'castopod' => 'Castopod',
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
]) ?>">
|
||||
<?= 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 ?>" />
|
||||
<img class="w-8 h-8 rounded-full" src="<?= $episode->podcast->cover->tiny_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', [
|
||||
|
@ -69,10 +69,10 @@
|
|||
</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-center bg-no-repeat bg-cover blur-lg mix-blend-soft-light" style="background-image: url('<?= $episode->podcast->banner->small_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" />
|
||||
<img src="<?= $episode->cover->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>
|
||||
|
@ -82,7 +82,7 @@
|
|||
<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" />
|
||||
<img src="<?= $person->avatar->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;
|
||||
}?>
|
||||
|
@ -102,7 +102,7 @@
|
|||
<div class="z-10 inline-flex items-center text-white gap-x-4">
|
||||
<play-episode-button
|
||||
id="<?= $episode->id ?>"
|
||||
imageSrc="<?= $episode->image->thumbnail_url ?>"
|
||||
imageSrc="<?= $episode->cover->thumbnail_url ?>"
|
||||
title="<?= $episode->title ?>"
|
||||
podcast="<?= $episode->podcast->title ?>"
|
||||
src="<?= $episode->audio_file_web_url ?>"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<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
|
||||
<img loading="lazy" src="<?= $episode->cover
|
||||
->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">
|
||||
|
@ -16,7 +16,7 @@
|
|||
</div>
|
||||
<play-episode-button
|
||||
id="<?= $episode->id ?>"
|
||||
imageSrc="<?= $episode->image->thumbnail_url ?>"
|
||||
imageSrc="<?= $episode->cover->thumbnail_url ?>"
|
||||
title="<?= $episode->title ?>"
|
||||
podcast="<?= $episode->podcast->title ?>"
|
||||
src="<?= $episode->audio_file_web_url ?>"
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<?= format_duration($episode->audio_file_duration) ?>
|
||||
</time>
|
||||
<img
|
||||
src="<?= $episode->image->thumbnail_url ?>"
|
||||
src="<?= $episode->cover->thumbnail_url ?>"
|
||||
alt="<?= $episode->title ?>" class="w-24 h-24"/>
|
||||
</div>
|
||||
<div class="flex flex-col flex-1 px-4 py-2">
|
||||
|
@ -17,7 +17,7 @@
|
|||
<play-episode-button
|
||||
class="mr-4"
|
||||
id="<?= $index . '_' . $episode->id ?>"
|
||||
imageSrc="<?= $episode->image->thumbnail_url ?>"
|
||||
imageSrc="<?= $episode->cover->thumbnail_url ?>"
|
||||
title="<?= $episode->title ?>"
|
||||
podcast="<?= $episode->podcast->title ?>"
|
||||
src="<?= $episode->audio_file_web_url ?>"
|
||||
|
|
|
@ -9,11 +9,10 @@
|
|||
<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" content="<?= $episode->cover->large_url ?>" />
|
||||
<meta property="og:image:width" content="<?= config('Images')
|
||||
->largeSize ?>" />
|
||||
<meta property="og:image:height" content="<?= config('Images')
|
||||
->largeSize ?>" />
|
||||
->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 ?>" />
|
||||
|
@ -23,7 +22,7 @@
|
|||
<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: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 ?>" />
|
||||
|
|
|
@ -11,11 +11,10 @@
|
|||
<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" content="<?= $episode->cover->large_url ?>" />
|
||||
<meta property="og:image:width" content="<?= config('Images')
|
||||
->largeSize ?>" />
|
||||
<meta property="og:image:height" content="<?= config('Images')
|
||||
->largeSize ?>" />
|
||||
->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 ?>" />
|
||||
|
@ -25,7 +24,7 @@
|
|||
<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: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 ?>" />
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
<article class="text-white">
|
||||
<div class="absolute bottom-0 left-0 z-10 w-full h-full backdrop-gradient"></div>
|
||||
<div class="w-full h-full overflow-hidden">
|
||||
<img alt="<?= $podcast->title ?>" src="<?= $podcast->image->medium_url ?>" class="object-cover w-full h-full transition duration-200 ease-in-out transform group-focus:scale-105 group-hover:scale-105" />
|
||||
<img alt="<?= $podcast->title ?>" src="<?= $podcast->cover->medium_url ?>" class="object-cover w-full h-full transition duration-200 ease-in-out transform group-focus:scale-105 group-hover:scale-105" />
|
||||
</div>
|
||||
<div class="absolute bottom-0 left-0 z-20 px-4 pb-2">
|
||||
<h2 class="font-bold leading-none truncate font-display"><?= $podcast->title ?></h2>
|
||||
|
|
|
@ -34,9 +34,10 @@
|
|||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<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 aspect-[3/1]" style="background-image: url('<?= $podcast->actor->cover_image_url ?>');">
|
||||
<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" />
|
||||
<header class="relative 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 aspect-[3/1]" style="background-image: url('<?= $podcast->banner->medium_url ?>');">
|
||||
<div class="absolute bottom-0 left-0 w-full h-full backdrop-gradient-pine"></div>
|
||||
<div class="z-10 flex items-center pl-4 -mb-6 md:pl-8 md:-mb-8 gap-x-4">
|
||||
<img src="<?= $podcast->cover->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', [
|
||||
|
@ -44,7 +45,7 @@
|
|||
]) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-flex items-center self-end mt-2 mb-2 mr-2 gap-x-2">
|
||||
<div class="z-10 inline-flex items-center self-end mt-2 mr-2 sm:mb-4 sm:mr-4 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; ?>
|
||||
|
|
|
@ -19,11 +19,9 @@
|
|||
<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 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')
|
||||
|
@ -55,7 +53,7 @@
|
|||
<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" />
|
||||
<img src="<?= $person->avatar->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;
|
||||
}?>
|
||||
|
|
|
@ -17,11 +17,9 @@
|
|||
<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 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')
|
||||
|
|
|
@ -17,11 +17,9 @@
|
|||
<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 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')
|
||||
|
|
Loading…
Reference in New Issue