Loading app/Resources/js/admin-audio-player.ts 0 → 100644 +79 −0 Original line number Diff line number Diff line import { VmAudio, VmCaptions, VmClickToPlay, VmControl, VmControls, VmCurrentTime, VmDefaultControls, VmDefaultSettings, VmDefaultUi, VmEndTime, VmFile, VmIcon, VmIconLibrary, VmLoadingScreen, VmMenu, VmMenuItem, VmMenuRadio, VmMenuRadioGroup, VmMuteControl, VmPlaybackControl, VmPlayer, VmScrubberControl, VmSettings, VmSettingsControl, VmSkeleton, VmSlider, VmSubmenu, VmTime, VmTimeProgress, VmTooltip, VmUi, VmVolumeControl, } from "@vime/core"; import "@vime/core/themes/default.css"; import "@vime/core/themes/light.css"; import "./modules/play-episode-button"; // Register Castopod's icons library const library: HTMLVmIconLibraryElement | null = document.querySelector( 'vm-icon-library[name="castopod-icons"]' ); if (library) { library.resolver = (iconName) => `/assets/icons/${iconName}.svg`; } // Vime elements for audio player customElements.define("vm-player", VmPlayer); customElements.define("vm-file", VmFile); customElements.define("vm-audio", VmAudio); customElements.define("vm-ui", VmUi); customElements.define("vm-default-ui", VmDefaultUi); customElements.define("vm-click-to-play", VmClickToPlay); customElements.define("vm-captions", VmCaptions); customElements.define("vm-loading-screen", VmLoadingScreen); customElements.define("vm-default-controls", VmDefaultControls); customElements.define("vm-default-settings", VmDefaultSettings); customElements.define("vm-controls", VmControls); customElements.define("vm-playback-control", VmPlaybackControl); customElements.define("vm-volume-control", VmVolumeControl); customElements.define("vm-scrubber-control", VmScrubberControl); customElements.define("vm-current-time", VmCurrentTime); customElements.define("vm-end-time", VmEndTime); customElements.define("vm-settings-control", VmSettingsControl); customElements.define("vm-time-progress", VmTimeProgress); customElements.define("vm-control", VmControl); customElements.define("vm-icon", VmIcon); customElements.define("vm-icon-library", VmIconLibrary); customElements.define("vm-tooltip", VmTooltip); customElements.define("vm-mute-control", VmMuteControl); customElements.define("vm-slider", VmSlider); customElements.define("vm-time", VmTime); customElements.define("vm-menu", VmMenu); customElements.define("vm-menu-item", VmMenuItem); customElements.define("vm-submenu", VmSubmenu); customElements.define("vm-menu-radio-group", VmMenuRadioGroup); customElements.define("vm-menu-radio", VmMenuRadio); customElements.define("vm-settings", VmSettings); customElements.define("vm-skeleton", VmSkeleton); app/Views/Components/DropdownMenu.php 0 → 100644 +51 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace App\Views\Components; use Exception; use ViewComponents\Component; class DropdownMenu extends Component { public string $id = ''; public array $items = []; public function setItems(string $value): void { $this->items = json_decode(html_entity_decode($value), true); } public function render(): string { if ($this->items === []) { throw new Exception('Dropdown menu has no items'); } $menuItems = ''; foreach ($this->items as $item) { switch ($item['type']) { case 'link': $menuItems .= anchor($item['uri'], $item['title'], [ 'class' => 'px-4 py-1 hover:bg-gray-100' . (array_key_exists('class', $item) ? ' ' . $item['class'] : ''), ]); break; case 'separator': $menuItems .= '<hr class="my-2 border border-gray-100">'; break; default: break; } } return <<<HTML <nav id="{$this->id}" class="absolute z-50 flex flex-col py-2 text-black whitespace-no-wrap bg-white border-black rounded-lg border-3" aria-labelledby="{$this->labeledBy}" data-dropdown="menu" data-dropdown-placement="bottom-end">{$menuItems}</nav> HTML; } } themes/cp_admin/_layout.php +22 −24 Original line number Diff line number Diff line Loading @@ -14,7 +14,7 @@ <?= service('vite') ->asset('js/admin.ts', 'js') ?> <?= service('vite') ->asset('js/audio-player.ts', 'js') ?> ->asset('js/admin-audio-player.ts', 'js') ?> </head> <body class="relative bg-pine-50 holy-grail-grid"> Loading @@ -40,28 +40,26 @@ data-dropdown="button" data-dropdown-target="my-account-dropdown-menu" aria-haspopup="true" aria-expanded="false"> <?= icon('account-circle', 'text-2xl opacity-60 mr-2') ?> <?= user() ->username ?> <?= icon('caret-down', 'ml-auto text-2xl') ?> </button> <nav id="my-account-dropdown-menu" class="absolute z-50 flex flex-col py-2 text-black whitespace-no-wrap bg-white border-black rounded border-[3px]" aria-labelledby="my-accountDropdown" data-dropdown="menu" data-dropdown-placement="bottom-end"> <a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to( 'my-account', ) ?>"><?= lang('AdminNavigation.account.my-account') ?></a> <a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to( 'change-password', ) ?>"><?= lang('AdminNavigation.account.change-password') ?></a> <a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to( 'logout', ) ?>"><?= lang('AdminNavigation.account.logout') ?></a> </nav> aria-expanded="false"><?= icon('account-circle', 'text-2xl opacity-60 mr-2') . user()->username . icon('caret-down', 'ml-auto text-2xl') ?></button> <DropdownMenu id="my-account-dropdown-menu" labeledBy="my-account-dropdown" items="<?= esc(json_encode([ [ 'type' => 'link', 'title' => lang('AdminNavigation.account.my-account'), 'uri' => route_to('my-account'), ], [ 'type' => 'link', 'title' => lang('AdminNavigation.account.change-password'), 'uri' => route_to('change-password'), ], [ 'type' => 'separator', ], [ 'type' => 'link', 'title' => lang('AdminNavigation.account.logout'), 'uri' => route_to('logout'), ], ])) ?>" /> </header> <aside id="admin-sidebar" class="sticky z-50 flex flex-col text-white transition duration-200 ease-in-out transform -translate-x-full border-r top-10 border-pine-900 bg-pine-800 holy-grail__sidebar md:translate-x-0"> <?php if (isset($podcast) && isset($episode)): ?> Loading @@ -80,7 +78,7 @@ </footer> </aside> <main class="relative holy-grail__main"> <header class="z-40 flex items-center bg-white border-b sticky-header-outer border-pine-100"> <header class="z-40 flex items-center px-4 bg-white border-b md:px-12 sticky-header-outer border-pine-100"> <div class="container flex flex-col justify-end mx-auto -mt-4 sticky-header-inner"> <?= render_breadcrumb('text-gray-800 text-xs items-center flex') ?> <div class="flex justify-between py-1"> Loading themes/cp_admin/episode/list.php +37 −36 Original line number Diff line number Diff line Loading @@ -74,44 +74,45 @@ [ 'header' => lang('Episode.list.actions'), 'cell' => function ($episode, $podcast) { return '<button id="more-dropdown-<?= $episode->id ?>" type="button" class="inline-flex items-center p-1 outline-none focus:ring" data-dropdown="button" data-dropdown-target="more-dropdown-<?= $episode->id ?>-menu" aria-haspopup="true" aria-expanded="false">' . return '<button id="more-dropdown-' . $episode->id . '" type="button" class="inline-flex items-center p-1 outline-none focus:ring" data-dropdown="button" data-dropdown-target="more-dropdown-' . $episode->id . '-menu" aria-haspopup="true" aria-expanded="false">' . icon('more') . '</button>' . '<nav id="more-dropdown-<?= $episode->id ?>-menu" class="flex flex-col py-2 text-black whitespace-no-wrap bg-white border rounded shadow" aria-labelledby="more-dropdown-<?= $episode->id ?>" data-dropdown="menu" data-dropdown-placement="bottom-start" data-dropdown-offset-x="0" data-dropdown-offset-y="-24">' . '<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to( 'episode-edit', $podcast->id, $episode->id, ) . '">' . lang('Episode.edit') . '</a>' . '<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to( 'embeddable-player-add', $podcast->id, $episode->id, ) . '">' . lang( 'Episode.embeddable_player.title', ) . '</a>' . '<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to( 'episode-persons-manage', $podcast->id, $episode->id, ) . '">' . lang('Person.persons') . '</a>' . '<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to( 'soundbites-edit', $podcast->id, $episode->id, ) . '">' . lang('Episode.soundbites') . '</a>' . '<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to( 'episode', $podcast->handle, $episode->slug, ) . '">' . lang('Episode.go_to_page') . '</a>' . '<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to( 'episode-delete', $podcast->id, $episode->id, ) . '">' . lang('Episode.delete') . '</a>' . '</nav>' . '</div>'; '<DropdownMenu id="more-dropdown-' . $episode->id . '-menu" labeledBy="more-dropdown-' . $episode->id . '" items="' . esc(json_encode([ [ 'type' => 'link', 'title' => lang('Episode.edit'), 'uri' => route_to('episode-edit', $podcast->id, $episode->id), ], [ 'type' => 'link', 'title' => lang('Episode.embeddable_player.title'), 'uri' => route_to('embeddable-player-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('Episode.soundbites'), 'uri' => route_to('soundbites-edit', $podcast->id, $episode->id), ], [ 'type' => 'link', 'title' => lang('Episode.go_to_page'), 'uri' => route_to('episode', $podcast->handle, $episode->slug), ], [ 'type' => 'separator', ], [ 'type' => 'link', 'title' => lang('Episode.delete'), 'uri' => route_to('episode-delete', $podcast->id, $episode->id), 'class' => 'font-semibold text-red-600', ], ])) . '" />'; }, ], ], Loading themes/cp_admin/podcast/latest_episodes.php +36 −35 Original line number Diff line number Diff line Loading @@ -52,41 +52,42 @@ aria-haspopup="true" aria-expanded="false" ><?= icon('more') ?></button> <nav id="more-dropdown-<?= $episode->id ?>-menu" class="z-50 flex flex-col py-2 text-black whitespace-no-wrap bg-white border rounded shadow" aria-labelledby="more-dropdown-<?= $episode->id ?>" data-dropdown="menu" data-dropdown-placement="bottom"> <a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to( 'episode-edit', $podcast->id, $episode->id, ) ?>"><?= lang('Episode.edit') ?></a> <a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to( 'embeddable-player-add', $podcast->id, $episode->id, ) ?>"><?= lang( 'Episode.embeddable_player.title', ) ?></a> <a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to( 'episode-persons-manage', $podcast->id, $episode->id, ) ?>"><?= lang('Person.persons') ?></a> <a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to( 'episode', $podcast->handle, $episode->slug, ) ?>"><?= lang('Episode.go_to_page') ?></a> <hr class="my-2 border border-gray-100"> <a class="px-4 py-1 font-semibold text-red-600 hover:bg-gray-100" href="<?= route_to( 'episode-delete', $podcast->id, $episode->id, ) ?>"><?= lang('Episode.delete') ?></a> </nav> <DropdownMenu id="more-dropdown-<?= $episode->id ?>-menu" labeledBy="more-dropdown-<?= $episode->id ?>" items="<?= esc(json_encode([ [ 'type' => 'link', 'title' => lang('Episode.edit'), 'uri' => route_to('episode-edit', $podcast->id, $episode->id), ], [ 'type' => 'link', 'title' => lang('Episode.embeddable_player.title'), 'uri' => route_to('embeddable-player-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('Episode.soundbites'), 'uri' => route_to('soundbites-edit', $podcast->id, $episode->id), ], [ 'type' => 'link', 'title' => lang('Episode.go_to_page'), 'uri' => route_to('episode', $podcast->handle, $episode->slug), ], [ 'type' => 'separator', ], [ 'type' => 'link', 'title' => lang('Episode.delete'), 'uri' => route_to('episode-delete', $podcast->id, $episode->id), 'class' => 'font-semibold text-red-600', ], ])) ?>" /> </div> </article> <?php endforeach; ?> Loading Loading
app/Resources/js/admin-audio-player.ts 0 → 100644 +79 −0 Original line number Diff line number Diff line import { VmAudio, VmCaptions, VmClickToPlay, VmControl, VmControls, VmCurrentTime, VmDefaultControls, VmDefaultSettings, VmDefaultUi, VmEndTime, VmFile, VmIcon, VmIconLibrary, VmLoadingScreen, VmMenu, VmMenuItem, VmMenuRadio, VmMenuRadioGroup, VmMuteControl, VmPlaybackControl, VmPlayer, VmScrubberControl, VmSettings, VmSettingsControl, VmSkeleton, VmSlider, VmSubmenu, VmTime, VmTimeProgress, VmTooltip, VmUi, VmVolumeControl, } from "@vime/core"; import "@vime/core/themes/default.css"; import "@vime/core/themes/light.css"; import "./modules/play-episode-button"; // Register Castopod's icons library const library: HTMLVmIconLibraryElement | null = document.querySelector( 'vm-icon-library[name="castopod-icons"]' ); if (library) { library.resolver = (iconName) => `/assets/icons/${iconName}.svg`; } // Vime elements for audio player customElements.define("vm-player", VmPlayer); customElements.define("vm-file", VmFile); customElements.define("vm-audio", VmAudio); customElements.define("vm-ui", VmUi); customElements.define("vm-default-ui", VmDefaultUi); customElements.define("vm-click-to-play", VmClickToPlay); customElements.define("vm-captions", VmCaptions); customElements.define("vm-loading-screen", VmLoadingScreen); customElements.define("vm-default-controls", VmDefaultControls); customElements.define("vm-default-settings", VmDefaultSettings); customElements.define("vm-controls", VmControls); customElements.define("vm-playback-control", VmPlaybackControl); customElements.define("vm-volume-control", VmVolumeControl); customElements.define("vm-scrubber-control", VmScrubberControl); customElements.define("vm-current-time", VmCurrentTime); customElements.define("vm-end-time", VmEndTime); customElements.define("vm-settings-control", VmSettingsControl); customElements.define("vm-time-progress", VmTimeProgress); customElements.define("vm-control", VmControl); customElements.define("vm-icon", VmIcon); customElements.define("vm-icon-library", VmIconLibrary); customElements.define("vm-tooltip", VmTooltip); customElements.define("vm-mute-control", VmMuteControl); customElements.define("vm-slider", VmSlider); customElements.define("vm-time", VmTime); customElements.define("vm-menu", VmMenu); customElements.define("vm-menu-item", VmMenuItem); customElements.define("vm-submenu", VmSubmenu); customElements.define("vm-menu-radio-group", VmMenuRadioGroup); customElements.define("vm-menu-radio", VmMenuRadio); customElements.define("vm-settings", VmSettings); customElements.define("vm-skeleton", VmSkeleton);
app/Views/Components/DropdownMenu.php 0 → 100644 +51 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace App\Views\Components; use Exception; use ViewComponents\Component; class DropdownMenu extends Component { public string $id = ''; public array $items = []; public function setItems(string $value): void { $this->items = json_decode(html_entity_decode($value), true); } public function render(): string { if ($this->items === []) { throw new Exception('Dropdown menu has no items'); } $menuItems = ''; foreach ($this->items as $item) { switch ($item['type']) { case 'link': $menuItems .= anchor($item['uri'], $item['title'], [ 'class' => 'px-4 py-1 hover:bg-gray-100' . (array_key_exists('class', $item) ? ' ' . $item['class'] : ''), ]); break; case 'separator': $menuItems .= '<hr class="my-2 border border-gray-100">'; break; default: break; } } return <<<HTML <nav id="{$this->id}" class="absolute z-50 flex flex-col py-2 text-black whitespace-no-wrap bg-white border-black rounded-lg border-3" aria-labelledby="{$this->labeledBy}" data-dropdown="menu" data-dropdown-placement="bottom-end">{$menuItems}</nav> HTML; } }
themes/cp_admin/_layout.php +22 −24 Original line number Diff line number Diff line Loading @@ -14,7 +14,7 @@ <?= service('vite') ->asset('js/admin.ts', 'js') ?> <?= service('vite') ->asset('js/audio-player.ts', 'js') ?> ->asset('js/admin-audio-player.ts', 'js') ?> </head> <body class="relative bg-pine-50 holy-grail-grid"> Loading @@ -40,28 +40,26 @@ data-dropdown="button" data-dropdown-target="my-account-dropdown-menu" aria-haspopup="true" aria-expanded="false"> <?= icon('account-circle', 'text-2xl opacity-60 mr-2') ?> <?= user() ->username ?> <?= icon('caret-down', 'ml-auto text-2xl') ?> </button> <nav id="my-account-dropdown-menu" class="absolute z-50 flex flex-col py-2 text-black whitespace-no-wrap bg-white border-black rounded border-[3px]" aria-labelledby="my-accountDropdown" data-dropdown="menu" data-dropdown-placement="bottom-end"> <a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to( 'my-account', ) ?>"><?= lang('AdminNavigation.account.my-account') ?></a> <a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to( 'change-password', ) ?>"><?= lang('AdminNavigation.account.change-password') ?></a> <a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to( 'logout', ) ?>"><?= lang('AdminNavigation.account.logout') ?></a> </nav> aria-expanded="false"><?= icon('account-circle', 'text-2xl opacity-60 mr-2') . user()->username . icon('caret-down', 'ml-auto text-2xl') ?></button> <DropdownMenu id="my-account-dropdown-menu" labeledBy="my-account-dropdown" items="<?= esc(json_encode([ [ 'type' => 'link', 'title' => lang('AdminNavigation.account.my-account'), 'uri' => route_to('my-account'), ], [ 'type' => 'link', 'title' => lang('AdminNavigation.account.change-password'), 'uri' => route_to('change-password'), ], [ 'type' => 'separator', ], [ 'type' => 'link', 'title' => lang('AdminNavigation.account.logout'), 'uri' => route_to('logout'), ], ])) ?>" /> </header> <aside id="admin-sidebar" class="sticky z-50 flex flex-col text-white transition duration-200 ease-in-out transform -translate-x-full border-r top-10 border-pine-900 bg-pine-800 holy-grail__sidebar md:translate-x-0"> <?php if (isset($podcast) && isset($episode)): ?> Loading @@ -80,7 +78,7 @@ </footer> </aside> <main class="relative holy-grail__main"> <header class="z-40 flex items-center bg-white border-b sticky-header-outer border-pine-100"> <header class="z-40 flex items-center px-4 bg-white border-b md:px-12 sticky-header-outer border-pine-100"> <div class="container flex flex-col justify-end mx-auto -mt-4 sticky-header-inner"> <?= render_breadcrumb('text-gray-800 text-xs items-center flex') ?> <div class="flex justify-between py-1"> Loading
themes/cp_admin/episode/list.php +37 −36 Original line number Diff line number Diff line Loading @@ -74,44 +74,45 @@ [ 'header' => lang('Episode.list.actions'), 'cell' => function ($episode, $podcast) { return '<button id="more-dropdown-<?= $episode->id ?>" type="button" class="inline-flex items-center p-1 outline-none focus:ring" data-dropdown="button" data-dropdown-target="more-dropdown-<?= $episode->id ?>-menu" aria-haspopup="true" aria-expanded="false">' . return '<button id="more-dropdown-' . $episode->id . '" type="button" class="inline-flex items-center p-1 outline-none focus:ring" data-dropdown="button" data-dropdown-target="more-dropdown-' . $episode->id . '-menu" aria-haspopup="true" aria-expanded="false">' . icon('more') . '</button>' . '<nav id="more-dropdown-<?= $episode->id ?>-menu" class="flex flex-col py-2 text-black whitespace-no-wrap bg-white border rounded shadow" aria-labelledby="more-dropdown-<?= $episode->id ?>" data-dropdown="menu" data-dropdown-placement="bottom-start" data-dropdown-offset-x="0" data-dropdown-offset-y="-24">' . '<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to( 'episode-edit', $podcast->id, $episode->id, ) . '">' . lang('Episode.edit') . '</a>' . '<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to( 'embeddable-player-add', $podcast->id, $episode->id, ) . '">' . lang( 'Episode.embeddable_player.title', ) . '</a>' . '<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to( 'episode-persons-manage', $podcast->id, $episode->id, ) . '">' . lang('Person.persons') . '</a>' . '<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to( 'soundbites-edit', $podcast->id, $episode->id, ) . '">' . lang('Episode.soundbites') . '</a>' . '<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to( 'episode', $podcast->handle, $episode->slug, ) . '">' . lang('Episode.go_to_page') . '</a>' . '<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to( 'episode-delete', $podcast->id, $episode->id, ) . '">' . lang('Episode.delete') . '</a>' . '</nav>' . '</div>'; '<DropdownMenu id="more-dropdown-' . $episode->id . '-menu" labeledBy="more-dropdown-' . $episode->id . '" items="' . esc(json_encode([ [ 'type' => 'link', 'title' => lang('Episode.edit'), 'uri' => route_to('episode-edit', $podcast->id, $episode->id), ], [ 'type' => 'link', 'title' => lang('Episode.embeddable_player.title'), 'uri' => route_to('embeddable-player-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('Episode.soundbites'), 'uri' => route_to('soundbites-edit', $podcast->id, $episode->id), ], [ 'type' => 'link', 'title' => lang('Episode.go_to_page'), 'uri' => route_to('episode', $podcast->handle, $episode->slug), ], [ 'type' => 'separator', ], [ 'type' => 'link', 'title' => lang('Episode.delete'), 'uri' => route_to('episode-delete', $podcast->id, $episode->id), 'class' => 'font-semibold text-red-600', ], ])) . '" />'; }, ], ], Loading
themes/cp_admin/podcast/latest_episodes.php +36 −35 Original line number Diff line number Diff line Loading @@ -52,41 +52,42 @@ aria-haspopup="true" aria-expanded="false" ><?= icon('more') ?></button> <nav id="more-dropdown-<?= $episode->id ?>-menu" class="z-50 flex flex-col py-2 text-black whitespace-no-wrap bg-white border rounded shadow" aria-labelledby="more-dropdown-<?= $episode->id ?>" data-dropdown="menu" data-dropdown-placement="bottom"> <a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to( 'episode-edit', $podcast->id, $episode->id, ) ?>"><?= lang('Episode.edit') ?></a> <a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to( 'embeddable-player-add', $podcast->id, $episode->id, ) ?>"><?= lang( 'Episode.embeddable_player.title', ) ?></a> <a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to( 'episode-persons-manage', $podcast->id, $episode->id, ) ?>"><?= lang('Person.persons') ?></a> <a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to( 'episode', $podcast->handle, $episode->slug, ) ?>"><?= lang('Episode.go_to_page') ?></a> <hr class="my-2 border border-gray-100"> <a class="px-4 py-1 font-semibold text-red-600 hover:bg-gray-100" href="<?= route_to( 'episode-delete', $podcast->id, $episode->id, ) ?>"><?= lang('Episode.delete') ?></a> </nav> <DropdownMenu id="more-dropdown-<?= $episode->id ?>-menu" labeledBy="more-dropdown-<?= $episode->id ?>" items="<?= esc(json_encode([ [ 'type' => 'link', 'title' => lang('Episode.edit'), 'uri' => route_to('episode-edit', $podcast->id, $episode->id), ], [ 'type' => 'link', 'title' => lang('Episode.embeddable_player.title'), 'uri' => route_to('embeddable-player-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('Episode.soundbites'), 'uri' => route_to('soundbites-edit', $podcast->id, $episode->id), ], [ 'type' => 'link', 'title' => lang('Episode.go_to_page'), 'uri' => route_to('episode', $podcast->handle, $episode->slug), ], [ 'type' => 'separator', ], [ 'type' => 'link', 'title' => lang('Episode.delete'), 'uri' => route_to('episode-delete', $podcast->id, $episode->id), 'class' => 'font-semibold text-red-600', ], ])) ?>" /> </div> </article> <?php endforeach; ?> Loading