Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • adaures/castopod
  • mkljczk/castopod-host
  • spaetz/castopod-host
  • PatrykMis/castopod
  • jonas/castopod
  • ajeremias/castopod
  • misuzu/castopod
  • KrzysztofDomanczyk/castopod
  • Behel/castopod
  • nebulon/castopod
  • ewen/castopod
  • NeoluxConsulting/castopod
  • nateritter/castopod-og
  • prcutler/castopod
14 results
Show changes
Showing
with 13 additions and 1496 deletions
import Time from "./modules/Time";
Time();
.chart-map {
height: 600px;
border: solid 10px #eee;
}
.chart-pie {
height: 450px;
width: 100%;
border: solid 1px #eee;
}
.chart-xy {
height: 500px;
width: 100%;
border: solid 1px #eee;
border: solid 3px #eee;
}
@import "./tailwind.css";
@import "./layout.css";
@import "./breadcrumb.css";
@import "./multiSelect.css";
@import "./radioBtn.css";
@import "./switch.css";
@import "./charts.css";
.holy-grail-grid {
@apply grid min-h-screen overflow-y-auto;
grid-template: 1fr auto / auto 1fr;
& .holy-grail-sidebar {
@apply w-64 col-start-1 col-end-2 row-start-1 row-end-3;
}
& .holy-grail-main {
@apply w-full col-start-1 col-end-3 row-start-1 row-end-2;
}
& .holy-grail-footer {
@apply w-full col-start-1 col-end-3 row-start-2 row-end-3;
}
@screen md {
& .holy-grail-main {
@apply col-start-2;
}
& .holy-grail-footer {
@apply col-start-2;
}
}
}
/*===============================
= MultiSelect =
===============================*/
.multiselect {
@apply relative;
&:focus {
@apply shadow-outline outline-none;
}
&:last-child {
@apply mb-0;
}
&.is-disabled {
&.multiselect__inner,
&.multiselect__input {
@apply bg-gray-300 cursor-not-allowed select-none;
}
&.multiselect__item {
@apply cursor-not-allowed;
}
}
& [hidden] {
@apply hidden;
}
}
.multiselect[data-type*="select-multiple"],
.multiselect[data-type*="text"] {
& .multiselect__inner {
@apply cursor-text;
}
& .multiselect__button {
@apply relative inline-block w-2 pl-4 mt-0 mb-0 ml-1 opacity-75;
background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjRkZGIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==);
background-size: 8px;
&:hover,
&:focus {
@apply opacity-100;
}
}
}
.multiselect__inner {
@apply inline-block w-full px-2 pt-2 pb-1 overflow-hidden align-top bg-white border rounded;
&.is-focused,
&.is-open {
@apply shadow-outline;
}
&.is-open {
@apply rounded-b-none;
}
&.is-flipped.is-open {
@apply rounded-t-none;
}
}
.multiselect__list {
@apply p-0 m-0 list-none;
}
.multiselect__list--multiple {
@apply inline;
& .multiselect__item {
@apply inline-flex px-2 py-1 mb-1 mr-2 text-sm text-white break-all bg-green-500 rounded;
&[data-deletable] {
@apply pr-1;
}
& [dir="rtl"] {
@apply ml-2 mr-0;
}
&.is-highlighted {
@apply bg-green-700;
}
&.is-disabled {
@apply bg-gray-500;
}
}
}
.multiselect__list--dropdown {
@apply absolute z-10 invisible w-full overflow-hidden break-all bg-white border border-t-0 rounded-b shadow-lg;
top: 100%;
will-change: visibility;
&.is-active {
@apply visible;
}
&.is-open {
@apply shadow-outline;
}
&.is-flipped {
@apply top-auto mt-0 rounded-t;
bottom: 100%;
}
& .multiselect__list {
@apply relative overflow-auto;
max-height: 300px;
-webkit-overflow-scrolling: touch;
will-change: scroll-position;
}
& .multiselect__item {
@apply relative p-3;
& [dir="rtl"] {
@apply text-right;
}
}
& .multiselect__item--selectable {
@screen sm {
padding-right: 100px;
&:after {
@apply absolute text-sm transform -translate-y-1/2 opacity-0;
content: attr(data-select-text);
right: 10px;
top: 50%;
}
& [dir="rtl"] {
@apply text-right;
padding-left: 100px;
padding-right: 10px;
&:after {
@apply right-auto;
left: 10px;
}
}
}
&.is-highlighted {
@apply bg-gray-100;
&:after {
@apply opacity-50;
}
}
}
}
.multiselect__item {
@apply cursor-default;
}
.multiselect__item--selectable {
@apply cursor-pointer;
}
.multiselect__item--disabled {
@apply opacity-50 cursor-not-allowed select-none;
}
.multiselect__heading {
@apply p-3 font-semibold text-gray-600 border-b;
}
.multiselect__button {
@apply bg-transparent bg-center bg-no-repeat border-0 appearance-none cursor-pointer;
text-indent: -9999px;
&:focus {
@apply outline-none;
}
}
.multiselect__input {
@apply inline-block max-w-full py-1 pl-1 mb-1 align-baseline bg-transparent border-0 rounded-none;
&:focus {
@apply outline-none;
}
& [dir="rtl"] {
@apply pl-0 pr-1;
}
}
.multiselect__placeholder {
@apply opacity-50;
}
/*===== End of Choices ======*/
.form-radio-btn {
@apply absolute opacity-0;
}
.form-radio-btn:focus + label {
@apply shadow-outline;
}
.form-radio-btn + label {
@apply inline-block px-2 py-1 text-sm text-black bg-white border rounded cursor-pointer;
&:hover {
@apply bg-green-100;
}
}
.form-radio-btn:checked + label {
@apply text-white bg-green-500;
&::before {
@apply mr-2 text-green-200;
content: "✓";
}
}
.form-switch {
@apply absolute w-0 h-0 opacity-0;
&:checked + .form-switch-slider {
@apply bg-green-500;
}
&:focus + .form-switch-slider {
@apply shadow-outline;
}
&:checked + .form-switch-slider::before {
@apply transform translate-x-5;
}
}
.form-switch-slider {
@apply relative inset-0 flex-shrink-0 w-10 h-5 transition duration-200 bg-gray-400 rounded-full cursor-pointer;
&::before {
@apply absolute w-4 h-4 transition duration-200 bg-white rounded-full shadow-xs;
content: "";
left: 2px;
bottom: 2px;
}
}
declare module "prosemirror-markdown";
declare module "prosemirror-example-setup";
<?= helper('page') ?>
<!DOCTYPE html>
<html lang="<?= service('request')->getLocale() ?>">
<head>
<meta charset="UTF-8"/>
<title><?= $this->renderSection('title') ?></title>
<meta name="description" content="Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience."/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="shortcut icon" type="image/png" href="/favicon.ico" />
<link rel="stylesheet" href="/assets/index.css"/>
</head>
<body class="flex flex-col min-h-screen mx-auto">
<header class="border-b">
<div class="container flex items-center justify-between px-2 py-4 mx-auto">
<a href="<?= route_to('home') ?>" class="text-2xl"><?= isset($page)
? $page->title
: 'Castopod' ?></a>
</div>
</header>
<main class="container flex-1 px-4 py-10 mx-auto">
<?= $this->renderSection('content') ?>
</main>
<footer class="container flex justify-between px-2 py-4 mx-auto text-sm text-right border-t">
<?= render_page_links() ?>
<small><?= lang('Common.powered_by', [
'castopod' =>
'<a class="underline hover:no-underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod</a>',
]) ?></small>
</footer>
</body>
<?php if (session()->has('message')): ?>
<div class="px-4 py-2 mb-4 font-semibold text-green-900 bg-green-200 border border-green-700">
<?= session('message') ?>
</div>
<?php declare(strict_types=1);
if (session()->has('message')): ?>
<Alert variant="success" class="mb-4"><?= session('message') ?></Alert>
<?php endif; ?>
<?php if (session()->has('error')): ?>
<div class="px-4 py-2 mb-4 font-semibold text-red-900 bg-red-200 border border-red-700">
<?= session('error') ?>
</div>
<Alert variant="danger" class="mb-4"><?= session('error') ?></Alert>
<?php endif; ?>
<?php if (session()->has('errors')): ?>
<ul class="px-4 py-2 mb-4 font-semibold text-red-900 bg-red-200 border border-red-700">
<?php foreach (session('errors') as $error): ?>
<li><?= $error ?></li>
<?php endforeach; ?>
</ul>
<?php endif;
?>
<Alert variant="danger" class="mb-4">
<ul>
<?php foreach (session('errors') as $error): ?>
<li><?= $error ?></li>
<?php endforeach; ?>
</ul>
</Alert>
<?php endif; ?>
<!DOCTYPE html>
<html lang="<?= service('request')->getLocale() ?>">
<head>
<meta charset="UTF-8"/>
<title><?= $this->renderSection('title') ?> | Castopod Admin</title>
<meta name="description" content="Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience."/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="shortcut icon" type="image/png" href="/favicon.ico" />
<link rel="stylesheet" href="/assets/admin.css"/>
<link rel="stylesheet" href="/assets/index.css"/>
<script src="/assets/admin.js" type="module" defer></script>
</head>
<body class="relative bg-gray-100 holy-grail-grid">
<div id="sidebar-backdrop" role="button" tabIndex="0" aria-label="Close" class="fixed z-50 hidden w-full h-full bg-gray-900 bg-opacity-50 md:hidden"></div>
<aside id="admin-sidebar" class="sticky top-0 z-50 flex flex-col w-64 max-h-screen transition duration-200 ease-in-out transform -translate-x-full bg-white border-r holy-grail-sidebar md:translate-x-0">
<?php if (isset($podcast)): ?>
<?= $this->include('admin/podcast/_sidebar') ?>
<?php else: ?>
<?= $this->include('admin/_sidebar') ?>
<?php endif; ?>
</aside>
<main class="overflow-hidden holy-grail-main">
<header class="text-white bg-gradient-to-tr from-gray-900 to-gray-800">
<div class="container flex flex-wrap items-end justify-between px-2 py-10 mx-auto md:px-12 gap-y-6 gap-x-6">
<div class="flex flex-col">
<?= render_breadcrumb('text-gray-300') ?>
<h1 class="text-3xl"><?= $this->renderSection(
'pageTitle'
) ?></h1>
</div>
<div class="flex flex-wrap"><?= $this->renderSection(
'headerRight'
) ?></div>
</div>
</header>
<div class="container px-2 py-8 mx-auto md:px-12">
<?= view('_message_block') ?>
<?= $this->renderSection('content') ?>
</div>
</main>
<footer class="px-2 py-2 mx-auto text-xs text-right holy-grail-footer">
<?= lang('Common.powered_by', [
'castopod' =>
'<a class="underline hover:no-underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod</a> v' .
CP_VERSION,
]) ?>
</footer>
<button
type="button"
id="sidebar-toggler"
class="fixed bottom-0 left-0 z-50 p-3 mb-3 ml-3 text-xl transition duration-300 ease-in-out bg-white border-2 rounded-full shadow-lg focus:outline-none md:hidden hover:bg-gray-100 focus:shadow-outline"
style="transform: translateX(0px);"><?= icon('menu') ?></button>
</body>
<div class="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500">
<?= lang('User.form.email') ?>
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<?= $user->email ?>
</dd>
</div>
<div class="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500">
<?= lang('User.form.username') ?>
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<?= $user->username ?>
</dd>
</div>
<div class="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500">
<?= lang('User.form.roles') ?>
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
[<?= implode(', ', $user->roles) ?>]
</dd>
</div>
<div class="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500">
<?= lang('User.form.permissions') ?>
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
[<?= implode(', ', $user->permissions) ?>]
</dd>
</div>
\ No newline at end of file
<?php
$navigation = [
'dashboard' => ['icon' => 'dashboard', 'items' => ['admin']],
'podcasts' => [
'icon' => 'mic',
'items' => ['podcast-list', 'podcast-create', 'podcast-import'],
],
'users' => ['icon' => 'group', 'items' => ['user-list', 'user-create']],
'pages' => ['icon' => 'pages', 'items' => ['page-list', 'page-create']],
]; ?>
<a href="<?= route_to(
'admin'
) ?>" class="inline-flex items-center px-4 py-2 mb-2 text-xl">
<?= svg('logo-castopod', 'h-8 mr-2') ?>
Castopod
</a>
<a href="<?= route_to(
'home'
) ?>" class="inline-flex items-center px-6 py-2 mb-2 text-sm underline outline-none hover:no-underline focus:shadow-outline">
<?= lang('AdminNavigation.go_to_website') ?>
<?= icon('external-link', 'ml-2 text-gray-500') ?>
</a>
<nav class="flex flex-col flex-1 overflow-y-auto">
<?php foreach ($navigation as $section => $data): ?>
<div class="mb-4">
<button class="inline-flex items-center w-full px-6 py-1 outline-none focus:shadow-outline" type="button">
<?= icon($data['icon'], 'text-gray-500') ?>
<span class="ml-2"><?= lang('AdminNavigation.' . $section) ?></span>
</button>
<ul>
<?php foreach ($data['items'] as $item): ?>
<?php $isActive = base_url(route_to($item)) == current_url(); ?>
<li>
<a class="block py-1 pl-12 pr-2 text-sm text-gray-600 outline-none hover:text-gray-900 focus:shadow-outline <?= $isActive
? 'font-semibold text-gray-900'
: '' ?>" href="<?= route_to($item) ?>"><?= lang(
'AdminNavigation.' . $item
) ?></a>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endforeach; ?>
</nav>
<div class="w-full mt-auto border-t" data-toggle="dropdown">
<button type="button" class="inline-flex items-center w-full px-6 py-2 outline-none focus:shadow-outline" id="my-accountDropdown" data-popper="button" aria-haspopup="true" aria-expanded="false">
<?= icon('user', 'text-gray-500 mr-2') ?>
<?= user()->username ?>
<?= icon('caret-right', 'ml-auto') ?>
</button>
<nav class="absolute z-50 flex-col hidden py-2 text-black whitespace-no-wrap bg-white border rounded shadow" aria-labelledby="my-accountDropdown" data-popper="menu" data-popper-placement="right-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>
</div>
<?= $this->extend('admin/_layout') ?>
<?= $this->section('title') ?>
<?= lang('Contributor.add_contributor', [$podcast->title]) ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= lang('Contributor.add_contributor', [$podcast->title]) ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<?= form_open(route_to('contributor-add', $podcast->id), [
'class' => 'flex flex-col max-w-sm',
]) ?>
<?= csrf_field() ?>
<?= form_label(lang('Contributor.form.user'), 'user') ?>
<?= form_dropdown('user', $userOptions, old('user'), [
'id' => 'user',
'class' => 'form-select mb-4',
'required' => 'required',
]) ?>
<?= form_label(lang('Contributor.form.role'), 'role') ?>
<?= form_dropdown('role', $roleOptions, old('role'), [
'id' => 'role',
'class' => 'form-select mb-4',
'required' => 'required',
]) ?>
<?= button(
lang('Contributor.form.submit_add'),
null,
['variant' => 'primary'],
['type' => 'submit', 'class' => 'self-end']
) ?>
<?= form_close() ?>
<?= $this->endSection() ?>
<?= $this->extend('admin/_layout') ?>
<?= $this->section('title') ?>
<?= lang('Contributor.edit_role', [$user->username]) ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= lang('Contributor.edit_role', [$user->username]) ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<?= form_open(route_to('contributor-edit', $podcast->id, $user->id), [
'class' => 'flex flex-col max-w-sm',
]) ?>
<?= csrf_field() ?>
<?= form_label(lang('Contributor.form.role'), 'role') ?>
<?= form_dropdown('role', $roleOptions, old('role', $contributorGroupId), [
'id' => 'role',
'class' => 'form-select mb-4',
'required' => 'required',
]) ?>
<?= button(
lang('Contributor.form.submit_edit'),
null,
['variant' => 'primary'],
['type' => 'submit', 'class' => 'self-end']
) ?>
<?= form_close() ?>
<?= $this->endSection() ?>
<?= $this->extend('admin/_layout') ?>
<?= $this->section('title') ?>
<?= lang('Contributor.podcast_contributors') ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= lang('Contributor.podcast_contributors') ?>
<?= $this->endSection() ?>
<?= $this->section('headerRight') ?>
<?= button(lang('Contributor.add'), route_to('contributor-add', $podcast->id), [
'variant' => 'primary',
'iconLeft' => 'add',
]) ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<?= data_table(
[
[
'header' => lang('Contributor.list.username'),
'cell' => function ($contributor) {
return $contributor->username;
},
],
[
'header' => lang('Contributor.list.role'),
'cell' => function ($contributor) {
return lang('Contributor.roles.' . $contributor->podcast_role);
},
],
[
'header' => lang('Common.actions'),
'cell' => function ($contributor, $podcast) {
return button(
lang('Contributor.edit'),
route_to(
'contributor-edit',
$podcast->id,
$contributor->id
),
[
'variant' => 'info',
'size' => 'small',
],
['class' => 'mr-2']
) .
button(
lang('Contributor.remove'),
route_to(
'contributor-remove',
$podcast->id,
$contributor->id
),
['variant' => 'danger', 'size' => 'small'],
['class' => 'mr-2']
);
},
],
],
$podcast->contributors,
$podcast
) ?>
<?= $this->endSection() ?>
<?= $this->extend('admin/_layout') ?>
<?= $this->section('title') ?>
<?= lang('Contributor.view', [
'username' => $contributor->username,
'podcastName' => $contributor->podcast->name,
]) ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<div class="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500">
Username
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<?= $contributor->username ?>
</dd>
</div>
<div class="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500">
Role
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<?= $contributor->podcast_role ?>
</dd>
</div>
<?= $this->endSection() ?>
<?= $this->extend('admin/_layout') ?>
<?= $this->section('title') ?>
<?= lang('Episode.create') ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= lang('Episode.create') ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<?= form_open_multipart(route_to('episode-create', $podcast->id), [
'method' => 'post',
'class' => 'flex flex-col',
]) ?>
<?= csrf_field() ?>
<?= form_hidden('client_timezone', 'UTC') ?>
<?= form_section(
lang('Episode.form.info_section_title'),
lang('Episode.form.info_section_subtitle')
) ?>
<?= form_label(
lang('Episode.form.enclosure'),
'enclosure',
[],
lang('Episode.form.enclosure_hint')
) ?>
<?= form_input([
'id' => 'enclosure',
'name' => 'enclosure',
'class' => 'form-input mb-4',
'required' => 'required',
'type' => 'file',
'accept' => '.mp3,.m4a',
]) ?>
<?= form_label(
lang('Episode.form.image'),
'image',
[],
lang('Episode.form.image_hint'),
true
) ?>
<?= form_input([
'id' => 'image',
'name' => 'image',
'class' => 'form-input',
'type' => 'file',
'accept' => '.jpg,.jpeg,.png',
]) ?>
<small class="mb-4 text-gray-600"><?= lang(
'Common.forms.image_size_hint'
) ?></small>
<?= form_label(
lang('Episode.form.title'),
'title',
[],
lang('Episode.form.title_hint')
) ?>
<?= form_input([
'id' => 'title',
'name' => 'title',
'class' => 'form-input mb-4',
'value' => old('title'),
'required' => 'required',
'data-slugify' => 'title',
]) ?>
<?= form_label(
lang('Episode.form.slug'),
'slug',
[],
lang('Episode.form.slug_hint')
) ?>
<?= form_input([
'id' => 'slug',
'name' => 'slug',
'class' => 'form-input mb-4',
'value' => old('slug'),
'required' => 'required',
'data-slugify' => 'slug',
]) ?>
<div class="flex flex-col mb-4 gap-x-2 gap-y-4 md:flex-row">
<div class="flex flex-col flex-1">
<?= form_label(lang('Episode.form.season_number'), 'season_number') ?>
<?= form_input([
'id' => 'season_number',
'name' => 'season_number',
'class' => 'form-input w-full',
'value' => old('season_number'),
'type' => 'number',
]) ?>
</div>
<div class="flex flex-col flex-1">
<?= form_label(lang('Episode.form.episode_number'), 'episode_number') ?>
<?= form_input([
'id' => 'episode_number',
'name' => 'episode_number',
'class' => 'form-input w-full',
'value' => old('episode_number'),
'type' => 'number',
]) ?>
</div>
</div>
<?= form_fieldset('', ['class' => 'mb-4']) ?>
<legend>
<?= lang('Episode.form.type.label') .
hint_tooltip(lang('Episode.form.type.hint'), 'ml-1') ?>
</legend>
<?= form_radio(
['id' => 'full', 'name' => 'type', 'class' => 'form-radio-btn'],
'full',
old('type') ? old('type') == 'full' : true
) ?>
<label for="full" class="inline-flex items-center">
<?= lang('Episode.form.type.full') ?>
</label>
<?= form_radio(
['id' => 'trailer', 'name' => 'type', 'class' => 'form-radio-btn'],
'trailer',
old('type') ? old('type') == 'trailer' : false
) ?>
<label for="trailer" class="inline-flex items-center">
<?= lang('Episode.form.type.trailer') ?>
</label>
<?= form_radio(
['id' => 'bonus', 'name' => 'type', 'class' => 'form-radio-btn'],
'bonus',
old('type') ? old('type') == 'bonus' : false
) ?>
<label for="bonus" class="inline-flex items-center">
<?= lang('Episode.form.type.bonus') ?>
</label>
<?= form_fieldset_close() ?>
<?= form_section_close() ?>
<?= form_section(
lang('Episode.form.show_notes_section_title'),
lang('Episode.form.show_notes_section_subtitle')
) ?>
<div class="mb-4">
<?= form_label(lang('Episode.form.description'), 'description') ?>
<?= form_textarea(
[
'id' => 'description',
'name' => 'description',
'class' => 'form-textarea',
'required' => 'required',
],
old('description', '', false),
'data-editor="markdown"'
) ?>
</div>
<div class="mb-4">
<?= form_label(
lang('Episode.form.description_footer'),
'description_footer',
[],
lang('Episode.form.description_footer_hint')
) ?>
<?= form_textarea(
[
'id' => 'description_footer',
'name' => 'description_footer',
'class' => 'form-textarea',
],
old(
'description_footer',
$podcast->episode_description_footer_markdown ?? '',
false
),
'data-editor="markdown"'
) ?>
</div>
<?= form_section_close() ?>
<?= form_section(
lang('Episode.form.location_section_title'),
lang('Episode.form.location_section_subtitle')
) ?>
<?= form_label(
lang('Episode.form.location_name'),
'location_name',
[],
lang('Episode.form.location_name_hint'),
true
) ?>
<?= form_input([
'id' => 'location_name',
'name' => 'location_name',
'class' => 'form-input mb-4',
'value' => old('location_name'),
]) ?>
<?= form_section_close() ?>
<?= form_section(
lang('Episode.form.publication_section_title'),
lang('Episode.form.publication_section_subtitle')
) ?>
<?= form_label(
lang('Episode.form.publication_date'),
'publication_date',
[],
lang('Episode.form.publication_date_hint')
) ?>
<div class="flex mb-4" data-picker="datetime">
<?= form_input([
'id' => 'publication_date',
'name' => 'publication_date',
'class' => 'form-input rounded-r-none flex-1',
'value' => old('publication_date', date('Y-m-d H:i')),
'data-input' => '',
]) ?>
<button
class="p-3 bg-green-100 border border-l-0 focus:outline-none rounded-r-md hover:bg-green-200 focus:shadow-outline"
type="button"
title="<?= lang('Episode.form.publication_date_clear') ?>"
data-clear=""><?= icon('close') ?></button>
</div>
<?= form_fieldset('', ['class' => 'flex mb-6 gap-1']) ?>
<legend>
<?= lang('Episode.form.parental_advisory.label') .
hint_tooltip(lang('Episode.form.type.hint'), 'ml-1') ?>
</legend>
<?= form_radio(
[
'id' => 'undefined',
'name' => 'parental_advisory',
'class' => 'form-radio-btn',
],
'undefined',
old('parental_advisory')
? old('parental_advisory') === 'undefined'
: true
) ?>
<label for="undefined"><?= lang(
'Episode.form.parental_advisory.undefined'
) ?></label>
<?= form_radio(
[
'id' => 'clean',
'name' => 'parental_advisory',
'class' => 'form-radio-btn',
],
'clean',
old('parental_advisory') ? old('parental_advisory') === 'clean' : false
) ?>
<label for="clean"><?= lang(
'Episode.form.parental_advisory.clean'
) ?></label>
<?= form_radio(
[
'id' => 'explicit',
'name' => 'parental_advisory',
'class' => 'form-radio-btn',
],
'explicit',
old('parental_advisory')
? old('parental_advisory') === 'explicit'
: false
) ?>
<label for="explicit"><?= lang(
'Episode.form.parental_advisory.explicit'
) ?></label>
<?= form_fieldset_close() ?>
<?= form_switch(
lang('Episode.form.block') .
hint_tooltip(lang('Episode.form.block_hint'), 'ml-1'),
['id' => 'block', 'name' => 'block'],
'yes',
old('block', false)
) ?>
<?= form_section_close() ?>
<?= form_section(
lang('Episode.form.additional_files_section_title'),
lang('Episode.form.additional_files_section_subtitle')
) ?>
<?= form_label(
lang('Episode.form.transcript'),
'transcript',
[],
lang('Episode.form.transcript_hint'),
true
) ?>
<?= form_input([
'id' => 'transcript',
'name' => 'transcript',
'class' => 'form-input mb-4',
'type' => 'file',
'accept' => '.txt,.html,.srt,.json',
]) ?>
<?= form_label(
lang('Episode.form.chapters'),
'chapters',
[],
lang('Episode.form.chapters_hint'),
true
) ?>
<?= form_input([
'id' => 'chapters',
'name' => 'chapters',
'class' => 'form-input mb-4',
'type' => 'file',
'accept' => '.json',
]) ?>
<?= form_section_close() ?>
<?= button(
lang('Episode.form.submit_create'),
null,
['variant' => 'primary'],
['type' => 'submit', 'class' => 'self-end']
) ?>
<?= form_close() ?>
<?= $this->endSection() ?>
<?= $this->extend('admin/_layout') ?>
<?= $this->section('title') ?>
<?= lang('Episode.edit') ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= lang('Episode.edit') ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<?= form_open_multipart(route_to('episode-edit', $podcast->id, $episode->id), [
'method' => 'post',
'class' => 'flex flex-col',
]) ?>
<?= csrf_field() ?>
<?= form_hidden('client_timezone', 'UTC') ?>
<?= form_section(
lang('Episode.form.info_section_title'),
lang('Episode.form.info_section_subtitle')
) ?>
<?= form_label(
lang('Episode.form.enclosure'),
'enclosure',
[],
lang('Episode.form.enclosure_hint')
) ?>
<?= form_input([
'id' => 'enclosure',
'name' => 'enclosure',
'class' => 'form-input mb-4',
'type' => 'file',
'accept' => '.mp3,.m4a',
]) ?>
<?= form_label(
lang('Episode.form.image'),
'image',
[],
lang('Episode.form.image_hint'),
true
) ?>
<img
src="<?= $episode->image->thumbnail_url ?>"
alt="<?= $episode->title ?>"
class="object-cover w-32 h-32"
/>
<?= form_input([
'id' => 'image',
'name' => 'image',
'class' => 'form-input',
'type' => 'file',
'accept' => '.jpg,.jpeg,.png',
]) ?>
<small class="mb-4 text-gray-600"><?= lang(
'Common.forms.image_size_hint'
) ?></small>
<?= form_label(
lang('Episode.form.title'),
'title',
[],
lang('Episode.form.title_hint')
) ?>
<?= form_input([
'id' => 'title',
'name' => 'title',
'class' => 'form-input mb-4',
'value' => old('title', $episode->title),
'required' => 'required',
'data-slugify' => 'title',
]) ?>
<?= form_label(
lang('Episode.form.slug'),
'slug',
[],
lang('Episode.form.slug_hint')
) ?>
<?= form_input([
'id' => 'slug',
'name' => 'slug',
'class' => 'form-input mb-4',
'value' => old('slug', $episode->slug),
'required' => 'required',
'data-slugify' => 'slug',
]) ?>
<div class="flex flex-col mb-4 gap-x-2 gap-y-4 md:flex-row">
<div class="flex flex-col flex-1">
<?= form_label(lang('Episode.form.season_number'), 'season_number') ?>
<?= form_input([
'id' => 'season_number',
'name' => 'season_number',
'class' => 'form-input w-full',
'value' => old('season_number', $episode->season_number),
'type' => 'number',
]) ?>
</div>
<div class="flex flex-col flex-1">
<?= form_label(lang('Episode.form.episode_number'), 'episode_number') ?>
<?= form_input([
'id' => 'episode_number',
'name' => 'episode_number',
'class' => 'form-input w-full',
'value' => old('episode_number', $episode->number),
'type' => 'number',
]) ?>
</div>
</div>
<?= form_fieldset('', ['class' => 'flex mb-4 gap-1']) ?>
<legend>
<?= lang('Episode.form.type.label') .
hint_tooltip(lang('Episode.form.type.hint'), 'ml-1') ?>
</legend>
<?= form_radio(
['id' => 'full', 'name' => 'type', 'class' => 'form-radio-btn'],
'full',
old('type') ? old('type') === 'full' : $episode->type === 'full'
) ?>
<label for="full" class="inline-flex items-center">
<?= lang('Episode.form.type.full') ?>
</label>
<?= form_radio(
['id' => 'trailer', 'name' => 'type', 'class' => 'form-radio-btn'],
'trailer',
old('type') ? old('type') === 'trailer' : $episode->type === 'trailer'
) ?>
<label for="trailer" class="inline-flex items-center">
<?= lang('Episode.form.type.trailer') ?>
</label>
<?= form_radio(
['id' => 'bonus', 'name' => 'type', 'class' => 'form-radio-btn'],
'bonus',
old('type') ? old('type') === 'bonus' : $episode->type === 'bonus'
) ?>
<label for="bonus" class="inline-flex items-center">
<?= lang('Episode.form.type.bonus') ?>
</label>
<?= form_fieldset_close() ?>
<?= form_section_close() ?>
<?= form_section(
lang('Episode.form.show_notes_section_title'),
lang('Episode.form.show_notes_section_subtitle')
) ?>
<div class="mb-4">
<?= form_label(lang('Episode.form.description'), 'description') ?>
<?= form_textarea(
[
'id' => 'description',
'name' => 'description',
'class' => 'form-textarea',
'required' => 'required',
],
old('description', $episode->description_markdown, false),
'data-editor="markdown"'
) ?>
</div>
<div class="mb-4">
<?= form_label(
lang('Episode.form.description_footer'),
'description_footer',
[],
lang('Episode.form.description_footer_hint')
) ?>
<?= form_textarea(
[
'id' => 'description_footer',
'name' => 'description_footer',
'class' => 'form-textarea',
],
old(
'description_footer',
$podcast->episode_description_footer_markdown ?? '',
false
),
'data-editor="markdown"'
) ?>
</div>
<?= form_section_close() ?>
<?= form_section(
lang('Episode.form.location_section_title'),
lang('Episode.form.location_section_subtitle')
) ?>
<?= form_label(
lang('Episode.form.location_name'),
'location_name',
[],
lang('Episode.form.location_name_hint'),
true
) ?>
<?= form_input([
'id' => 'location_name',
'name' => 'location_name',
'class' => 'form-input mb-4',
'value' => old('location_name', $episode->location_name),
]) ?>
<?= form_section_close() ?>
<?= form_section(
lang('Episode.form.publication_section_title'),
lang('Episode.form.publication_section_subtitle')
) ?>
<?= form_label(
lang('Episode.form.publication_date'),
'publication_date',
[],
lang('Episode.form.publication_date_hint')
) ?>
<div class="flex mb-4" data-picker="datetime">
<?= form_input([
'id' => 'publication_date',
'name' => 'publication_date',
'class' => 'form-input rounded-r-none flex-1',
'value' => old(
'publication_date',
$episode->published_at
? $episode->published_at->format('Y-m-d H:i')
: ''
),
'data-input' => '',
]) ?>
<button
class="p-3 bg-green-100 border border-l-0 focus:outline-none rounded-r-md hover:bg-green-200 focus:shadow-outline"
type="button"
title="<?= lang('Episode.form.publication_date_clear') ?>"
data-clear=""><?= icon('close') ?></button>
</div>
<?= form_fieldset('', ['class' => 'mb-6']) ?>
<legend>
<?= lang('Episode.form.parental_advisory.label') .
hint_tooltip(lang('Episode.form.type.hint'), 'ml-1') ?>
</legend>
<?= form_radio(
[
'id' => 'undefined',
'name' => 'parental_advisory',
'class' => 'form-radio-btn',
],
'undefined',
old('parental_advisory')
? old('parental_advisory') === 'undefined'
: $episode->parental_advisory === null
) ?>
<label for="undefined"><?= lang(
'Episode.form.parental_advisory.undefined'
) ?></label>
<?= form_radio(
[
'id' => 'clean',
'name' => 'parental_advisory',
'class' => 'form-radio-btn',
],
'clean',
old('parental_advisory')
? old('parental_advisory') === 'clean'
: $episode->parental_advisory === 'clean'
) ?>
<label for="clean"><?= lang(
'Episode.form.parental_advisory.clean'
) ?></label>
<?= form_radio(
[
'id' => 'explicit',
'name' => 'parental_advisory',
'class' => 'form-radio-btn',
],
'explicit',
old('parental_advisory')
? old('parental_advisory') === 'explicit'
: $episode->parental_advisory === 'explicit'
) ?>
<label for="explicit"><?= lang(
'Episode.form.parental_advisory.explicit'
) ?></label>
<?= form_fieldset_close() ?>
<?= form_switch(
lang('Episode.form.block') .
hint_tooltip(lang('Episode.form.block_hint'), 'ml-1'),
['id' => 'block', 'name' => 'block'],
'yes',
old('block', $episode->is_blocked)
) ?>
<?= form_section_close() ?>
<?= form_section(
lang('Episode.form.additional_files_section_title'),
lang('Episode.form.additional_files_section_subtitle', [
'podcastNamespaceLink' =>
'“<a href="https://github.com/Podcastindex-org/podcast-namespace" target="_blank" rel="noreferrer noopener" style="text-decoration: underline;">podcast namespace</a>”',
])
) ?>
<div class="flex flex-col flex-1">
<?= form_label(
lang('Episode.form.transcript'),
'transcript',
[],
lang('Episode.form.transcript_hint'),
true
) ?>
<?php if ($episode->transcript): ?>
<div class="flex justify-between">
<?= anchor(
$episode->transcriptUrl,
icon('file', 'mr-2') . $episode->transcript,
[
'class' => 'inline-flex items-center text-xs',
'target' => '_blank',
'rel' => 'noreferrer noopener',
]
) .
anchor(
route_to('transcript-delete', $podcast->id, $episode->id),
icon('delete-bin', 'mx-auto'),
[
'class' =>
'p-1 bg-red-200 rounded-full text-red-700 hover:text-red-900',
'data-toggle' => 'tooltip',
'data-placement' => 'bottom',
'title' => lang('Episode.form.transcript_delete'),
]
) ?>
</div>
<?php endif; ?>
<?= form_input([
'id' => 'transcript',
'name' => 'transcript',
'class' => 'form-input mb-4',
'type' => 'file',
'accept' => '.txt,.html,.srt,.json',
]) ?>
</div>
<div class="flex flex-col flex-1">
<?= form_label(
lang('Episode.form.chapters'),
'chapters',
[],
lang('Episode.form.chapters_hint'),
true
) ?>
<?php if ($episode->chapters): ?>
<div class="flex justify-between">
<?= anchor(
$episode->chaptersUrl,
icon('file', 'mr-2') . $episode->chapters,
[
'class' => 'inline-flex items-center text-xs',
'target' => '_blank',
'rel' => 'noreferrer noopener',
]
) .
anchor(
route_to('chapters-delete', $podcast->id, $episode->id),
icon('delete-bin', 'mx-auto'),
[
'class' =>
'p-1 bg-red-200 rounded-full text-red-700 hover:text-red-900',
'data-toggle' => 'tooltip',
'data-placement' => 'bottom',
'title' => lang('Episode.form.chapters_delete'),
]
) ?>
</div>
<?php endif; ?>
<?= form_input([
'id' => 'chapters',
'name' => 'chapters',
'class' => 'form-input mb-4',
'type' => 'file',
'accept' => '.json',
]) ?>
</div>
<?= form_section_close() ?>
<?= button(
lang('Episode.form.submit_edit'),
null,
['variant' => 'primary'],
['type' => 'submit', 'class' => 'self-end']
) ?>
<?= form_close() ?>
<?= $this->endSection() ?>
<?= $this->extend('admin/_layout') ?>
<?= $this->section('title') ?>
<?= lang('Episode.all_podcast_episodes') ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= lang('Episode.all_podcast_episodes') ?> (<?= $pager->getDetails()[
'total'
] ?>)
<?= $this->endSection() ?>
<?= $this->section('headerRight') ?>
<?= button(
lang('Episode.create'),
route_to('episode-create', $podcast->id),
['variant' => 'primary', 'iconLeft' => 'add']
) ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<p class="mb-4 text-sm italic text-gray-700"><?= lang('Common.pageInfo', [
'currentPage' => $pager->getDetails()['currentPage'],
'pageCount' => $pager->getDetails()['pageCount'],
]) ?></p>
<div class="flex flex-wrap mb-6">
<?php if ($episodes): ?>
<?php foreach ($episodes as $episode): ?>
<article class="flex w-full max-w-lg p-4 mx-auto">
<img
loading="lazy"
src="<?= $episode->image->thumbnail_url ?>"
alt="<?= $episode->title ?>" class="object-cover w-20 h-20 mr-2 rounded-lg" />
<div class="flex flex-col flex-1">
<div class="flex">
<a class="flex-1 text-sm hover:underline" href="<?= route_to(
'episode-view',
$podcast->id,
$episode->id
) ?>">
<h2 class="inline-flex justify-between w-full font-bold leading-none group">
<span class="mr-1 group-hover:underline"><?= $episode->title ?></span>
<?= episode_numbering(
$episode->number,
$episode->season_number,
'text-xs font-bold text-gray-600',
true
) ?>
</h2>
</a>
<div class="relative" data-toggle="dropdown">
<button type="button" class="inline-flex items-center p-1 outline-none focus:shadow-outline" id="moreDropdown" data-popper="button" aria-haspopup="true" aria-expanded="false">
<?= icon('more') ?>
</button>
<nav class="absolute z-10 flex-col hidden py-2 text-black whitespace-no-wrap bg-white border rounded shadow" aria-labelledby="moreDropdown" data-popper="menu" data-popper-placement="bottom-end" data-popper-offset-x="0" data-popper-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(
'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->name,
$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>
</div>
<div class="mb-2 text-xs">
<?= publication_pill(
$episode->published_at,
$episode->publication_status
) ?>
<span class="mx-1"></span>
<time datetime="PT<?= $episode->enclosure_duration ?>S">
<?= format_duration($episode->enclosure_duration) ?>
</time>
</div>
<audio controls preload="none" class="w-full mt-auto">
<source src="/<?= $episode->enclosure_media_path ?>" type="<?= $episode->enclosure_type ?>">
Your browser does not support the audio tag.
</audio>
</div>
</article>
<?php endforeach; ?>
<?php else: ?>
<p class="italic"><?= lang('Podcast.no_episode') ?></p>
<?php endif; ?>
</div>
<?= $pager->links() ?>
<?= $this->endSection() ?>