mirror of
https://code.castopod.org/adaures/castopod
synced 2025-04-19 13:01:19 +00:00
feat(episodes): replace soft delete with permanent delete
+ add constraint to prevent deleting an episode when published
This commit is contained in:
parent
0345728739
commit
eb9ff522c2
@ -147,10 +147,6 @@ class AddEpisodes extends Migration
|
||||
'updated_at' => [
|
||||
'type' => 'DATETIME',
|
||||
],
|
||||
'deleted_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
]);
|
||||
$this->forge->addPrimaryKey('id');
|
||||
$this->forge->addUniqueKey(['podcast_id', 'slug']);
|
||||
|
@ -207,12 +207,6 @@ class AuthSeeder extends Seeder
|
||||
],
|
||||
[
|
||||
'name' => 'delete',
|
||||
'description' =>
|
||||
'Delete an episode of a podcast without removing it from the database',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'delete_permanently',
|
||||
'description' =>
|
||||
'Delete all occurrences of an episode of a podcast from the database',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
|
@ -78,7 +78,6 @@ use RuntimeException;
|
||||
* @property Time|null $published_at;
|
||||
* @property Time $created_at;
|
||||
* @property Time $updated_at;
|
||||
* @property Time|null $deleted_at;
|
||||
*
|
||||
* @property Person[] $persons;
|
||||
* @property Soundbite[] $soundbites;
|
||||
|
@ -10,6 +10,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Entities\Media;
|
||||
|
||||
use App\Models\MediaModel;
|
||||
use CodeIgniter\Database\BaseResult;
|
||||
use CodeIgniter\Entity\Entity;
|
||||
use CodeIgniter\Files\File;
|
||||
|
||||
@ -101,9 +103,15 @@ class BaseMedia extends Entity
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function deleteFile(): void
|
||||
public function deleteFile(): bool
|
||||
{
|
||||
helper('media');
|
||||
unlink(media_path($this->file_path));
|
||||
return unlink(media_path($this->file_path));
|
||||
}
|
||||
|
||||
public function delete(): bool|BaseResult
|
||||
{
|
||||
$mediaModel = new MediaModel();
|
||||
return $mediaModel->delete($this->id, true);
|
||||
}
|
||||
}
|
||||
|
@ -69,11 +69,13 @@ class Image extends BaseMedia
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function deleteFile(): void
|
||||
public function deleteFile(): bool
|
||||
{
|
||||
parent::deleteFile();
|
||||
if (parent::deleteFile()) {
|
||||
return $this->deleteSizes();
|
||||
}
|
||||
|
||||
$this->deleteSizes();
|
||||
return false;
|
||||
}
|
||||
|
||||
public function saveSizes(): void
|
||||
@ -89,12 +91,16 @@ class Image extends BaseMedia
|
||||
}
|
||||
}
|
||||
|
||||
private function deleteSizes(): void
|
||||
private function deleteSizes(): bool
|
||||
{
|
||||
// delete all derived sizes
|
||||
foreach (array_keys($this->sizes) as $name) {
|
||||
$pathProperty = $name . '_path';
|
||||
unlink(media_path($this->{$pathProperty}));
|
||||
if (! unlink(media_path($this->{$pathProperty}))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -63,12 +63,16 @@ class Transcript extends BaseMedia
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function deleteFile(): void
|
||||
public function deleteFile(): bool
|
||||
{
|
||||
parent::deleteFile();
|
||||
if (! parent::deleteFile()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->json_path) {
|
||||
unlink($this->json_path);
|
||||
return unlink($this->json_path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -327,6 +327,18 @@ class Podcast extends Entity
|
||||
return $this->episodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the podcast's episodes count
|
||||
*/
|
||||
public function getEpisodesCount(): int|string
|
||||
{
|
||||
if ($this->id === null) {
|
||||
throw new RuntimeException('Podcast must be created before getting number of episodes.');
|
||||
}
|
||||
|
||||
return (new EpisodeModel())->getPodcastEpisodesCount($this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the podcast's persons
|
||||
*
|
||||
|
@ -94,11 +94,6 @@ class EpisodeModel extends Model
|
||||
*/
|
||||
protected $returnType = Episode::class;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $useSoftDeletes = true;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
@ -249,6 +244,18 @@ class EpisodeModel extends Model
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of episodes of a podcast
|
||||
*/
|
||||
public function getPodcastEpisodesCount(int $podcastId): int|string
|
||||
{
|
||||
return $this
|
||||
->where([
|
||||
'podcast_id' => $podcastId,
|
||||
])
|
||||
->countAllResults();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timestamp difference in seconds between the next episode to publish and the current timestamp Returns
|
||||
* false if there's no episode to publish
|
||||
|
@ -38,6 +38,7 @@ class Button extends Component
|
||||
'danger' => 'text-white bg-red-600 hover:bg-red-700',
|
||||
'warning' => 'text-black bg-yellow-500 hover:bg-yellow-600',
|
||||
'info' => 'text-white bg-blue-500 hover:bg-blue-600',
|
||||
'disabled' => 'text-black bg-gray-300 cursor-not-allowed',
|
||||
];
|
||||
|
||||
$sizeClass = [
|
||||
|
@ -131,6 +131,18 @@ class EpisodeController extends BaseController
|
||||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
if ((new EpisodeModel())
|
||||
->where([
|
||||
'slug' => $this->request->getPost('slug'),
|
||||
'podcast_id' => $this->podcast->id,
|
||||
])
|
||||
->first()) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('error', lang('Episode.messages.sameSlugError'));
|
||||
}
|
||||
|
||||
$db = db_connect();
|
||||
$db->transStart();
|
||||
|
||||
@ -681,6 +693,11 @@ class EpisodeController extends BaseController
|
||||
->with('errors', $episodeModel->errors());
|
||||
}
|
||||
|
||||
// set podcast is_published_on_hubs to false to trigger websub push
|
||||
(new PodcastModel())->update($this->episode->podcast->id, [
|
||||
'is_published_on_hubs' => false,
|
||||
]);
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id]);
|
||||
@ -715,43 +732,74 @@ class EpisodeController extends BaseController
|
||||
->with('errors', $this->validator->getErrors());
|
||||
}
|
||||
|
||||
if ($this->episode->published_at !== null) {
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('error', lang('Episode.messages.deletePublishedEpisodeError'));
|
||||
}
|
||||
|
||||
$audio = $this->episode->audio;
|
||||
|
||||
$db = db_connect();
|
||||
|
||||
$db->transStart();
|
||||
|
||||
$allPostsLinkedToEpisode = (new PostModel())
|
||||
->where([
|
||||
'episode_id' => $this->episode->id,
|
||||
])
|
||||
->findAll();
|
||||
foreach ($allPostsLinkedToEpisode as $post) {
|
||||
(new PostModel())->removePost($post);
|
||||
$episodeModel = new EpisodeModel();
|
||||
|
||||
if (! $episodeModel->delete($this->episode->id)) {
|
||||
$db->transRollback();
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $episodeModel->errors());
|
||||
}
|
||||
|
||||
// set podcast is_published_on_hubs to false to trigger websub push
|
||||
(new PodcastModel())->update($this->episode->podcast->id, [
|
||||
'is_published_on_hubs' => false,
|
||||
]);
|
||||
$episodeMediaList = [$this->episode->transcript, $this->episode->chapters, $audio];
|
||||
|
||||
$episodeModel = new EpisodeModel();
|
||||
if ($this->episode->published_at !== null) {
|
||||
// if episode is published, set episode published_at to null to unpublish before deletion
|
||||
$this->episode->published_at = null;
|
||||
//only delete episode cover if different from podcast's
|
||||
if ($this->episode->cover_id !== null) {
|
||||
$episodeMediaList[] = $this->episode->cover;
|
||||
}
|
||||
|
||||
if (! $episodeModel->update($this->episode->id, $this->episode)) {
|
||||
//delete episode media records from database
|
||||
foreach ($episodeMediaList as $episodeMedia) {
|
||||
if ($episodeMedia !== null && ! $episodeMedia->delete()) {
|
||||
$db->transRollback();
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput()
|
||||
->with('errors', $episodeModel->errors());
|
||||
->with('error', lang('Episode.messages.deleteError', [
|
||||
'type' => $episodeMedia->type,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
$episodeModel->delete($this->episode->id);
|
||||
$warnings = [];
|
||||
|
||||
//remove episode media files from disk
|
||||
foreach ($episodeMediaList as $episodeMedia) {
|
||||
if ($episodeMedia !== null && ! $episodeMedia->deleteFile()) {
|
||||
$warnings[] = lang('Episode.messages.deleteFileError', [
|
||||
'type' => $episodeMedia->type,
|
||||
'file_path' => $episodeMedia->file_path,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$db->transComplete();
|
||||
|
||||
return redirect()->route('episode-list', [$this->podcast->id]);
|
||||
if ($warnings !== []) {
|
||||
return redirect()
|
||||
->route('episode-list', [$this->podcast->id])
|
||||
->with('message', lang('Episode.messages.deleteSuccess'))
|
||||
->with('warnings', $warnings);
|
||||
}
|
||||
|
||||
return redirect()->route('episode-list', [$this->podcast->id])->with(
|
||||
'message',
|
||||
lang('Episode.messages.deleteSuccess')
|
||||
);
|
||||
}
|
||||
|
||||
public function embed(): string
|
||||
|
@ -47,6 +47,24 @@ return [
|
||||
'createSuccess' => 'Episode has been successfully created!',
|
||||
'editSuccess' => 'Episode has been successfully updated!',
|
||||
'publishCancelSuccess' => 'Episode publication successfully cancelled!',
|
||||
'unpublishBeforeDeleteTip' => 'You must unpublish the episode before deleting it.',
|
||||
'deletePublishedEpisodeError' => 'Please unpublish the episode before deleting it.',
|
||||
'deleteSuccess' => 'Episode successfully deleted!',
|
||||
'deleteError' => 'Failed to delete episode {type, select,
|
||||
transcript {transcript}
|
||||
chapters {chapters}
|
||||
image {cover}
|
||||
audio {audio}
|
||||
other {media}
|
||||
}.',
|
||||
'deleteFileError' => 'Failed to delete {type, select,
|
||||
transcript {transcript}
|
||||
chapters {chapters}
|
||||
image {cover}
|
||||
audio {audio}
|
||||
other {media}
|
||||
} file {file_path}. You must manually remove it from your disk.',
|
||||
'sameSlugError' => 'An episode with the chosen slug already exists.',
|
||||
],
|
||||
'form' => [
|
||||
'file_size_error' =>
|
||||
@ -147,7 +165,7 @@ return [
|
||||
],
|
||||
'delete_form' => [
|
||||
'disclaimer' =>
|
||||
"Deleting the episode will delete all the posts associated with it and remove it from the podcast's RSS feed.",
|
||||
"Deleting the episode will delete all media files, comments, video clips and soundbites associated with it.",
|
||||
'understand' => 'I understand, I want to delete the episode',
|
||||
'submit' => 'Delete',
|
||||
],
|
||||
|
@ -147,7 +147,7 @@ return [
|
||||
],
|
||||
'delete_form' => [
|
||||
'disclaimer' =>
|
||||
"Supprimer l’épisode supprimera toutes les publications qui lui sont associées et le retirera du flux RSS du podcast.",
|
||||
"Supprimer l’épisode supprimera tous les fichiers multimédia, commentaires, extraits vidéos et extraits sonores qui lui sont associés.",
|
||||
'understand' => 'Je comprends, je veux supprimer l’épisode',
|
||||
'submit' => 'Supprimer',
|
||||
],
|
||||
|
@ -195,12 +195,6 @@ class AuthSeeder extends Seeder
|
||||
],
|
||||
[
|
||||
'name' => 'delete',
|
||||
'description' =>
|
||||
'Delete an episode of a podcast without removing it from the database',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
],
|
||||
[
|
||||
'name' => 'delete_permanently',
|
||||
'description' =>
|
||||
'Delete all occurrences of an episode of a podcast from the database',
|
||||
'has_permission' => ['podcast_admin'],
|
||||
|
@ -28,8 +28,6 @@ class AddIsPublishedOnHubsToPodcasts extends Migration
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$prefix = $this->db->getPrefix();
|
||||
|
||||
$this->forge->dropColumn($prefix . 'podcasts', 'is_published_on_hubs');
|
||||
$this->forge->dropColumn('podcasts', 'is_published_on_hubs');
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +28,6 @@ class AddIsPublishedOnHubsToEpisodes extends Migration
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$prefix = $this->db->getPrefix();
|
||||
|
||||
$this->forge->dropColumn($prefix . 'episodes', 'is_published_on_hubs');
|
||||
$this->forge->dropColumn('episodes', 'is_published_on_hubs');
|
||||
}
|
||||
}
|
||||
|
@ -17,3 +17,13 @@ if (session()->has('message')): ?>
|
||||
</ul>
|
||||
</Alert>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (session()->has('warnings')): ?>
|
||||
<Alert variant="warning" class="mb-4">
|
||||
<ul>
|
||||
<?php foreach (session('warnings') as $warning): ?>
|
||||
<li><?= esc($warning) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</Alert>
|
||||
<?php endif; ?>
|
||||
|
@ -11,7 +11,7 @@
|
||||
</div>
|
||||
</a>
|
||||
<button class="absolute top-0 right-0 z-10 p-2 mt-2 mr-2 text-white transition -translate-y-12 rounded-full opacity-0 focus:ring-accent focus:opacity-100 focus:-translate-y-0 group-hover:translate-y-0 bg-black/50 group-hover:opacity-100" id="more-dropdown-<?= $episode->id ?>" data-dropdown="button" data-dropdown-target="more-dropdown-<?= $episode->id ?>-menu" aria-haspopup="true" aria-expanded="false" title="<?= lang('Common.more') ?>"><?= icon('more') ?></button>
|
||||
<DropdownMenu id="more-dropdown-<?= $episode->id ?>-menu" labelledby="more-dropdown-<?= $episode->id ?>" offsetY="-32" items="<?= esc(json_encode([
|
||||
<?php $items = [
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.go_to_page'),
|
||||
@ -45,11 +45,24 @@
|
||||
[
|
||||
'type' => 'separator',
|
||||
],
|
||||
[
|
||||
];
|
||||
if ($episode->published_at === null) {
|
||||
$items[] = [
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.delete'),
|
||||
'uri' => route_to('episode-delete', $episode->podcast->id, $episode->id),
|
||||
'class' => 'font-semibold text-red-600',
|
||||
],
|
||||
])) ?>" />
|
||||
];
|
||||
} else {
|
||||
$label = lang('Episode.delete');
|
||||
$icon = icon('forbid', 'mr-2');
|
||||
$title = lang('Episode.messages.unpublishBeforeDeleteTip');
|
||||
$items[] = [
|
||||
'type' => 'html',
|
||||
'content' => esc(<<<CODE_SAMPLE
|
||||
<span class="inline-flex items-center px-4 py-1 font-semibold text-gray-400 cursor-not-allowed" data-tooltip="bottom" title="{$title}">{$icon}{$label}</span>
|
||||
CODE_SAMPLE),
|
||||
];
|
||||
} ?>
|
||||
<DropdownMenu id="more-dropdown-<?= $episode->id ?>-menu" labelledby="more-dropdown-<?= $episode->id ?>" offsetY="-32" items="<?= esc(json_encode($items)) ?>" />
|
||||
</article>
|
@ -278,7 +278,11 @@
|
||||
|
||||
</form>
|
||||
|
||||
<Button class="mt-8" variant="danger" uri="<?= route_to('episode-delete', $podcast->id, $episode->id) ?>" iconLeft="delete-bin"><?= lang('Episode.delete') ?></Button>
|
||||
<?php if ($episode->published_at === null): ?>
|
||||
<Button class="mt-8" variant="danger" uri="<?= route_to('episode-delete', $podcast->id, $episode->id) ?>" iconLeft="delete-bin"><?= lang('Episode.delete') ?></Button>
|
||||
<?php else: ?>
|
||||
<Button class="mt-8" variant="disabled" iconLeft="forbid" data-tooltip="right" title="<?= lang('Episode.messages.unpublishBeforeDeleteTip') ?>"><?= lang('Episode.delete') ?></Button>
|
||||
<?php endif ?>
|
||||
|
||||
|
||||
<?= $this->endSection() ?>
|
||||
|
@ -73,50 +73,63 @@
|
||||
[
|
||||
'header' => lang('Episode.list.actions'),
|
||||
'cell' => function ($episode, $podcast) {
|
||||
$items = [
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.go_to_page'),
|
||||
'uri' => route_to('episode', esc($podcast->handle), esc($episode->slug)),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.edit'),
|
||||
'uri' => route_to('episode-edit', $podcast->id, $episode->id),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.embed.title'),
|
||||
'uri' => route_to('embed-add', $podcast->id, $episode->id),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Person.persons'),
|
||||
'uri' => route_to('episode-persons-manage', $podcast->id, $episode->id),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('VideoClip.list.title'),
|
||||
'uri' => route_to('video-clips-list', $episode->podcast->id, $episode->id),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Soundbite.list.title'),
|
||||
'uri' => route_to('soundbites-list', $podcast->id, $episode->id),
|
||||
],
|
||||
[
|
||||
'type' => 'separator',
|
||||
],
|
||||
];
|
||||
if ($episode->published_at === null) {
|
||||
$items[] = [
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.delete'),
|
||||
'uri' => route_to('episode-delete', $podcast->id, $episode->id),
|
||||
'class' => 'font-semibold text-red-600',
|
||||
];
|
||||
} else {
|
||||
$label = lang('Episode.delete');
|
||||
$icon = icon('forbid');
|
||||
$title = lang('Episode.messages.unpublishBeforeDeleteTip');
|
||||
$items[] = [
|
||||
'type' => 'html',
|
||||
'content' => esc(<<<CODE_SAMPLE
|
||||
<span class="inline-flex items-center px-4 py-1 font-semibold text-gray-400 cursor-not-allowed" data-tooltip="bottom" title="{$title}">{$icon}<span class="ml-2">{$label}</span></span>
|
||||
CODE_SAMPLE),
|
||||
];
|
||||
}
|
||||
return '<button id="more-dropdown-' . $episode->id . '" type="button" class="inline-flex items-center p-1 rounded-full focus:ring-accent" data-dropdown="button" data-dropdown-target="more-dropdown-' . $episode->id . '-menu" aria-haspopup="true" aria-expanded="false">' .
|
||||
icon('more') .
|
||||
'</button>' .
|
||||
'<DropdownMenu id="more-dropdown-' . $episode->id . '-menu" labelledby="more-dropdown-' . $episode->id . '" offsetY="-24" items="' . esc(json_encode([
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.go_to_page'),
|
||||
'uri' => route_to('episode', esc($podcast->handle), esc($episode->slug)),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.edit'),
|
||||
'uri' => route_to('episode-edit', $podcast->id, $episode->id),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.embed.title'),
|
||||
'uri' => route_to('embed-add', $podcast->id, $episode->id),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Person.persons'),
|
||||
'uri' => route_to('episode-persons-manage', $podcast->id, $episode->id),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('VideoClip.list.title'),
|
||||
'uri' => route_to('video-clips-list', $episode->podcast->id, $episode->id),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Soundbite.list.title'),
|
||||
'uri' => route_to('soundbites-list', $podcast->id, $episode->id),
|
||||
],
|
||||
[
|
||||
'type' => 'separator',
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.delete'),
|
||||
'uri' => route_to('episode-delete', $podcast->id, $episode->id),
|
||||
'class' => 'font-semibold text-red-600',
|
||||
],
|
||||
])) . '" />';
|
||||
'<DropdownMenu id="more-dropdown-' . $episode->id . '-menu" labelledby="more-dropdown-' . $episode->id . '" offsetY="-24" items="' . esc(json_encode($items)) . '" />';
|
||||
},
|
||||
],
|
||||
],
|
||||
|
@ -33,7 +33,13 @@ $podcastNavigation = [
|
||||
'platforms-funding',
|
||||
],
|
||||
],
|
||||
]; ?>
|
||||
];
|
||||
|
||||
$counts = [
|
||||
'episode-list' => $podcast->getEpisodesCount(),
|
||||
];
|
||||
|
||||
?>
|
||||
|
||||
<div class="flex items-center px-4 py-2 border-b border-navigation">
|
||||
<img
|
||||
@ -65,13 +71,19 @@ $podcastNavigation = [
|
||||
<ul class="flex flex-col">
|
||||
<?php foreach ($data['items'] as $item): ?>
|
||||
<?php $isActive = url_is(route_to($item, $podcast->id)); ?>
|
||||
<?php
|
||||
$itemLabel = lang('PodcastNavigation.' . $item);
|
||||
if (array_key_exists($item, $counts)) {
|
||||
$itemLabel .= ' (' . $counts[$item] . ')';
|
||||
}
|
||||
?>
|
||||
<li class="inline-flex">
|
||||
<a class="w-full py-1 pl-14 pr-2 text-sm hover:opacity-100 focus:ring-inset focus:ring-accent <?= $isActive
|
||||
? 'font-semibold opacity-100 inline-flex items-center'
|
||||
: 'opacity-75' ?>" href="<?= route_to(
|
||||
$item,
|
||||
$podcast->id,
|
||||
) ?>"><?= ($isActive ? icon('chevron-right', 'mr-2') : '') . lang('PodcastNavigation.' . $item) ?></a>
|
||||
) ?>"><?= ($isActive ? icon('chevron-right', 'mr-2') : '') . $itemLabel ?></a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
|
Loading…
x
Reference in New Issue
Block a user