feat(ui): create ViewComponents library to enable building class and view files components

- replace some helper components and forms with class components in the ui
- create viewcomponents
service and load the component function to be used in views
This commit is contained in:
Yassine Doghri 2021-08-19 14:00:14 +00:00
parent fcecbe1c68
commit 94872f2338
41 changed files with 1323 additions and 692 deletions

View File

@ -46,6 +46,7 @@ class Autoload extends AutoloadConfig
'Config' => APPPATH . 'Config', 'Config' => APPPATH . 'Config',
'ActivityPub' => APPPATH . 'Libraries/ActivityPub', 'ActivityPub' => APPPATH . 'Libraries/ActivityPub',
'Analytics' => APPPATH . 'Libraries/Analytics', 'Analytics' => APPPATH . 'Libraries/Analytics',
'ViewComponents' => APPPATH . 'Libraries/ViewComponents',
]; ];
/** /**
@ -84,5 +85,5 @@ class Autoload extends AutoloadConfig
* ``` * ```
* @var array<int, string> * @var array<int, string>
*/ */
public $files = []; public $files = [APPPATH . 'Libraries/ViewComponents/Helpers/view_components_helper.php'];
} }

View File

@ -110,7 +110,9 @@ if (! function_exists('button')) {
CODE_SAMPLE; CODE_SAMPLE;
} }
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
if (! function_exists('icon_button')) { if (! function_exists('icon_button')) {
/** /**
* Icon Button component * Icon Button component
@ -145,6 +147,7 @@ if (! function_exists('icon_button')) {
} }
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
if (! function_exists('hint_tooltip')) { if (! function_exists('hint_tooltip')) {
/** /**
* Hint component * Hint component
@ -167,7 +170,9 @@ if (! function_exists('hint_tooltip')) {
return $tooltip . '">' . icon('question') . '</span>'; return $tooltip . '">' . icon('question') . '</span>';
} }
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
if (! function_exists('data_table')) { if (! function_exists('data_table')) {
/** /**
* Data table component * Data table component
@ -223,7 +228,9 @@ if (! function_exists('data_table')) {
'</div>'; '</div>';
} }
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
if (! function_exists('publication_pill')) { if (! function_exists('publication_pill')) {
/** /**
* Publication pill component * Publication pill component
@ -250,7 +257,9 @@ if (! function_exists('publication_pill')) {
'</span>'; '</span>';
} }
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
if (! function_exists('publication_button')) { if (! function_exists('publication_button')) {
/** /**
* Publication button component * Publication button component
@ -508,27 +517,5 @@ if (! function_exists('relative_time')) {
CODE_SAMPLE; CODE_SAMPLE;
} }
} }
// ------------------------------------------------------------------------
if (! function_exists('xml_editor')) {
/**
* XML Editor field
*
* @param array<string, mixed> $customData
* @param array<string, mixed> $extra
*/
function xml_editor(array $customData = [], string $value = '', array $extra = []): string
{
$defaultData = [
'slot' => 'textarea',
'rows' => 5,
];
$data = array_merge($defaultData, $customData);
$textarea = form_textarea($data, $value, $extra);
return <<<CODE_SAMPLE
<xml-editor>{$textarea}</time-ago>
CODE_SAMPLE;
}
}
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------

View File

@ -141,38 +141,12 @@ if (! function_exists('form_label')) {
//-------------------------------------------------------------------- //--------------------------------------------------------------------
if (! function_exists('form_multiselect')) {
/**
* Multi-select menu
*
* @param array<string, string> $options
* @param string[] $selected
* @param array<string, string> $customExtra
*/
function form_multiselect(
string $name = '',
array $options = [],
array $selected = [],
array $customExtra = []
): string {
$defaultExtra = [
'data-class' => $customExtra['class'],
'multiple' => 'multiple',
];
$extra = array_merge($defaultExtra, $customExtra);
return form_dropdown($name, $options, $selected, $extra);
}
}
//--------------------------------------------------------------------
if (! function_exists('form_dropdown')) { if (! function_exists('form_dropdown')) {
/** /**
* Drop-down Menu (based on html select tag) * Drop-down Menu (based on html select tag)
* *
* @param array<string, mixed> $options * @param array<string, mixed> $options
* @param string[] $selected * @param array<string|int> $selected
* @param array<string, mixed> $customExtra * @param array<string, mixed> $customExtra
*/ */
function form_dropdown( function form_dropdown(
@ -236,81 +210,3 @@ if (! function_exists('form_dropdown')) {
return $form . "</select>\n"; return $form . "</select>\n";
} }
} }
//--------------------------------------------------------------------
if (! function_exists('form_editor')) {
/**
* Markdown editor
*
* @param array<string, mixed> $data
* @param array<string, mixed>|string $extra
*/
function form_markdown_editor(array $data = [], string $value = '', string | array $extra = ''): string
{
$editorClass = 'w-full flex flex-col bg-white border border-gray-500 focus-within:ring-1 focus-within:ring-blue-600';
if (array_key_exists('class', $data) && $data['class'] !== '') {
$editorClass .= ' ' . $data['class'];
unset($data['class']);
}
$data['class'] = 'border-none outline-none focus:border-none focus:outline-none w-full h-full';
return '<div class="' . $editorClass . '">' .
'<header class="sticky top-0 z-20 flex flex-wrap justify-between bg-white border-b border-gray-500">' .
'<markdown-write-preview for="' . $data['id'] . '" class="relative inline-flex h-8">' .
'<button type="button" slot="write" class="px-2 font-semibold focus:outline-none focus:ring-inset focus:ring-2 focus:ring-pine-600">' . lang(
'Common.forms.editor.write'
) . '</button>' .
'<button type="button" slot="preview" class="px-2 focus:outline-none focus:ring-inset focus:ring-2 focus:ring-pine-600">' . lang(
'Common.forms.editor.preview'
) . '</button>' .
'</markdown-write-preview>' .
'<markdown-toolbar for="' . $data['id'] . '" class="flex gap-4 px-2 py-1">' .
'<div class="inline-flex text-2xl gap-x-1">' .
'<md-header class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'heading'
) . '</md-header>' .
'<md-bold class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'bold'
) . '</md-bold>' .
'<md-italic class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'italic'
) . '</md-italic>' .
'</div>' .
'<div class="inline-flex text-2xl gap-x-1">' .
'<md-unordered-list class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'list-unordered'
) . '</md-unordered-list>' .
'<md-ordered-list class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'list-ordered'
) . '</md-ordered-list>' .
'</div>' .
'<div class="inline-flex text-2xl gap-x-1">' .
'<md-quote class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'quote'
) . '</md-quote>' .
'<md-link class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'link'
) . '</md-link>' .
'<md-image class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'image-add'
) . '</md-image>' .
'</div>' .
'</markdown-toolbar>' .
'</header>' .
'<div class="relative">' .
form_textarea($data, $value, $extra) .
'<markdown-preview for="' . $data['id'] . '" class="absolute top-0 left-0 hidden w-full h-full p-2 overflow-y-auto prose bg-gray-50" showClass="bg-white"></markdown-preview>' .
'</div>' .
'<footer class="flex px-2 py-1 bg-gray-100 border-t">' .
'<a href="https://commonmark.org/help/" class="inline-flex items-center text-xs font-semibold text-gray-500 hover:text-gray-700" target="_blank" rel="noopener noreferrer">' . icon(
'markdown',
'mr-1 text-lg text-gray-400'
) . lang('Common.forms.editor.help') . '</a>' .
'</footer>' .
'</div>';
}
}
// ------------------------------------------------------------------------

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace ViewComponents;
class Component implements ComponentInterface
{
/**
* @var array<string, string>
*/
protected array $attributes = [
'class' => '',
];
/**
* @param array<string, mixed> $properties
* @param array<string, string> $attributes
*/
public function __construct(
protected array $properties,
array $attributes
) {
// overwrite default properties if set
foreach ($properties as $key => $value) {
$this->{$key} = $value;
}
$this->attributes = array_merge($this->attributes, $attributes);
}
public function render(): string
{
return static::class . ': RENDER METHOD NOT IMPLEMENTED';
}
}

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace ViewComponents;
interface ComponentInterface
{
public function render(): string;
}

View File

@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace ViewComponents;
use ViewComponents\Config\ViewComponents;
use ViewComponents\Exceptions\ComponentNotFoundException;
class ComponentLoader
{
protected ViewComponents $config;
protected string $name;
/**
* @var array<string, mixed>
*/
protected array $properties = [];
/**
* @var array<string, string>
*/
protected array $attributes = [];
public function __construct()
{
$this->config = config('ViewComponents');
}
public function __get(string $property): mixed
{
if (property_exists($this, $property)) {
return $this->{$property};
}
}
// @phpstan-ignore-next-line
public function __set(string $property, mixed $value)
{
if (property_exists($this, $property)) {
$this->{$property} = $value;
}
return $this;
}
/**
* @throws ComponentNotFoundException
*/
public function load(): string
{
// first, check if there exists a component class to load in class components path
if (file_exists("{$this->config->classComponentsPath}/{$this->name}.php")) {
return $this->loadComponentClass();
}
// check for the existence of a view file if no component class has been found
// component view files are camel case
$camelCaseName = strtolower(preg_replace('~(?<!^)(?<!\/)[A-Z]~', '_$0', $this->name) ?? '');
if (file_exists("{$this->config->componentsViewPath}/{$camelCaseName}.php")) {
return $this->loadComponentView($camelCaseName);
}
throw new ComponentNotFoundException("Could not find component \"{$this->name}\"");
}
private function loadComponentClass(): string
{
$classComponentsNamespace = $this->config->classComponentsNamespace;
$namespacedName = str_replace('/', '\\', $this->name);
$componentClassNamespace = "{$classComponentsNamespace}\\{$namespacedName}";
$component = new $componentClassNamespace($this->properties, $this->attributes);
return $component->render();
}
private function loadComponentView(string $name): string
{
$viewData = [...$this->properties, ...$this->attributes];
return view("components/{$name}", $viewData);
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace ViewComponents\Config;
use CodeIgniter\Config\BaseService;
use ViewComponents\ComponentLoader;
/**
* Services Configuration file.
*
* Services are simply other classes/libraries that the system uses to do its job. This is used by CodeIgniter to allow
* the core of the framework to be swapped out easily without affecting the usage within the rest of your application.
*
* This file holds any application-specific services, or service overrides that you might need. An example has been
* included with the general method format you should use for your service methods. For more examples, see the core
* Services file at system/Config/Services.php.
*/
class Services extends BaseService
{
public static function viewcomponents(bool $getShared = true): ComponentLoader
{
if ($getShared) {
return self::getSharedInstance('viewcomponents');
}
return new ComponentLoader();
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace ViewComponents\Config;
use CodeIgniter\Config\BaseConfig;
class ViewComponents extends BaseConfig
{
public string $classComponentsNamespace = APP_NAMESPACE . '\View\Components';
public string $classComponentsPath = APPPATH . 'View/Components';
public string $componentsViewPath = APPPATH . 'Views/components';
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace ViewComponents\Exceptions;
use CodeIgniter\Exceptions\ExceptionInterface;
use RuntimeException;
class ComponentNotFoundException extends RuntimeException implements ExceptionInterface
{
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
/**
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
if (! function_exists('component')) {
/**
* Loads the specified class or view file component in the parameters
*
* @param array<string, array<string, mixed>> $properties
* @param array<string, array<string, mixed>> $attributes
*/
function component(string $name, array $properties = [], array $attributes = []): string
{
$componentLoader = service('viewcomponents');
$componentLoader->name = $name;
$componentLoader->properties = $properties;
$componentLoader->attributes = $attributes;
return $componentLoader->load();
}
}

View File

@ -60,6 +60,10 @@ export class XMLEditor extends LitElement {
border: 1px solid #6b7280; border: 1px solid #6b7280;
background-color: #ffffff; background-color: #ffffff;
} }
.cm-editor.cm-focused {
outline: 2px solid transparent;
box-shadow: 0 0 0 1px #2563eb;
}
`; `;
render(): TemplateResult<1> { render(): TemplateResult<1> {

View File

@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
namespace App\View\Components;
use ViewComponents\Component;
class Button extends Component
{
protected string $label = '';
protected string $uri = '';
protected string $variant = 'default';
protected string $size = 'base';
protected string $iconLeft = '';
protected string $iconRight = '';
protected bool $isSquared = false;
public function render(): string
{
$baseClass =
'inline-flex items-center font-semibold shadow-xs rounded-full focus:outline-none focus:ring';
$variantClass = [
'default' => 'text-black bg-gray-300 hover:bg-gray-400',
'primary' => 'text-white bg-pine-700 hover:bg-pine-800',
'secondary' => 'text-white bg-gray-700 hover:bg-gray-800',
'accent' => 'text-white bg-rose-600 hover:bg-rose-800',
'success' => 'text-white bg-green-600 hover:bg-green-700',
'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',
];
$sizeClass = [
'small' => 'text-xs md:text-sm',
'base' => 'text-sm md:text-base',
'large' => 'text-lg md:text-xl',
];
$basePaddings = [
'small' => 'px-2 md:px-3 md:py-1',
'base' => 'px-3 py-1 md:px-4 md:py-2',
'large' => 'px-3 py-2 md:px-5',
];
$squaredPaddings = [
'small' => 'p-1',
'base' => 'p-2',
'large' => 'p-3',
];
$buttonClass =
$baseClass .
' ' .
($this->isSquared
? $squaredPaddings[$this->size]
: $basePaddings[$this->size]) .
' ' .
$sizeClass[$this->size] .
' ' .
$variantClass[$this->variant];
if (array_key_exists('class', $this->attributes)) {
$buttonClass .= ' ' . $this->attributes['class'];
unset($this->attributes['class']);
}
if ($this->iconLeft !== '') {
$this->label = icon($this->iconLeft, 'mr-2') . $this->label;
}
if ($this->iconRight !== '') {
$this->label .= icon($this->iconRight, 'ml-2');
}
if ($this->uri !== '') {
return anchor($this->uri, $this->label, array_merge([
'class' => $buttonClass,
], $this->attributes));
}
$defaultButtonAttributes = [
'type' => 'button',
];
$attributes = stringify_attributes(array_merge($defaultButtonAttributes, $this->attributes));
return <<<CODE_SAMPLE
<button class="{$buttonClass}" {$attributes}>{$this->label}</button>
CODE_SAMPLE;
}
}

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace App\View\Components\Forms;
use ViewComponents\Component;
class Input extends Component
{
public function render(): string
{
return '';
}
}

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace App\View\Components\Forms;
use ViewComponents\Component;
class Label extends Component
{
/**
* @var array<string, string>
*/
protected array $attributes = [
'for' => '',
'name' => '',
'value' => '',
'class' => '',
];
protected string $text = '';
protected string $hint = '';
protected bool $isOptional = false;
public function render(): string
{
$labelClass = $this->attributes['class'];
unset($this->attributes['class']);
$attributes = stringify_attributes($this->attributes);
$optionalText = $this->isOptional ? '<small class="ml-1 lowercase">(' .
lang('Common.optional') .
')</small>' : '';
$hint = $this->hint !== '' ? hint_tooltip($this->hint, 'ml-1') : '';
return <<<CODE_SAMPLE
<label class="{$labelClass}" {$attributes}>{$this->text}{$optionalText}{$hint}</label>
CODE_SAMPLE;
}
}

View File

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace App\View\Components\Forms;
use ViewComponents\Component;
class MarkdownEditor extends Component
{
protected string $content = '';
public function render(): string
{
$editorClass = 'w-full flex flex-col bg-white border border-gray-500 focus-within:ring-1 focus-within:ring-blue-600';
if ($this->attributes['class'] !== '') {
$editorClass .= ' ' . $this->attributes['class'];
unset($this->attributes['class']);
}
$this->attributes['class'] = 'border-none outline-none focus:border-none focus:outline-none w-full h-full';
return '<div class="' . $editorClass . '">' .
'<header class="sticky top-0 z-20 flex flex-wrap justify-between bg-white border-b border-gray-500">' .
'<markdown-write-preview for="' . $this->attributes['id'] . '" class="relative inline-flex h-8">' .
'<button type="button" slot="write" class="px-2 font-semibold focus:outline-none focus:ring-inset focus:ring-2 focus:ring-pine-600">' . lang(
'Common.forms.editor.write'
) . '</button>' .
'<button type="button" slot="preview" class="px-2 focus:outline-none focus:ring-inset focus:ring-2 focus:ring-pine-600">' . lang(
'Common.forms.editor.preview'
) . '</button>' .
'</markdown-write-preview>' .
'<markdown-toolbar for="' . $this->attributes['id'] . '" class="flex gap-4 px-2 py-1">' .
'<div class="inline-flex text-2xl gap-x-1">' .
'<md-header class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'heading'
) . '</md-header>' .
'<md-bold class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'bold'
) . '</md-bold>' .
'<md-italic class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'italic'
) . '</md-italic>' .
'</div>' .
'<div class="inline-flex text-2xl gap-x-1">' .
'<md-unordered-list class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'list-unordered'
) . '</md-unordered-list>' .
'<md-ordered-list class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'list-ordered'
) . '</md-ordered-list>' .
'</div>' .
'<div class="inline-flex text-2xl gap-x-1">' .
'<md-quote class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'quote'
) . '</md-quote>' .
'<md-link class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'link'
) . '</md-link>' .
'<md-image class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon(
'image-add'
) . '</md-image>' .
'</div>' .
'</markdown-toolbar>' .
'</header>' .
'<div class="relative">' .
form_textarea($this->attributes, $this->content) .
'<markdown-preview for="' . $this->attributes['id'] . '" class="absolute top-0 left-0 hidden w-full h-full p-2 overflow-y-auto prose bg-gray-50" showClass="bg-white"></markdown-preview>' .
'</div>' .
'<footer class="flex px-2 py-1 bg-gray-100 border-t">' .
'<a href="https://commonmark.org/help/" class="inline-flex items-center text-xs font-semibold text-gray-500 hover:text-gray-700" target="_blank" rel="noopener noreferrer">' . icon(
'markdown',
'mr-1 text-lg text-gray-400'
) . lang('Common.forms.editor.help') . '</a>' .
'</footer>' .
'</div>';
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace App\View\Components\Forms;
use ViewComponents\Component;
class MultiSelect extends Component
{
/**
* @var array<string, string>
*/
protected array $options = [];
/**
* @var string[]
*/
protected array $selected = [];
public function render(): string
{
$defaultAttributes = [
'data-class' => $this->attributes['class'],
'multiple' => 'multiple',
];
$extra = array_merge($defaultAttributes, $this->attributes);
return form_dropdown($this->attributes['name'], $this->options, $this->selected, $extra);
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace App\View\Components\Forms;
use ViewComponents\Component;
class MultiSelect extends Component
{
/**
* @var array<string, string>
*/
protected array $options = [];
/**
* @var string[]
*/
protected array $selected = [];
public function render(): string
{
$defaultAttributes = [
'data-class' => $this->attributes['class'],
'multiple' => 'multiple',
];
$extra = array_merge($defaultAttributes, $this->attributes);
return form_dropdown($this->attributes['name'], $this->options, $this->selected, $extra);
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace App\View\Components\Forms;
use ViewComponents\Component;
/**
* Form Checkbox Switch
*
* Abstracts form_label to stylize it as a switch toggle
*/
class Toggler extends Component
{
/**
* @var array<string, string>
*/
protected array $attributes = [
'id' => '',
'name' => '',
'value' => '',
'class' => '',
];
protected string $label = '';
protected string $hint = '';
protected bool $checked = false;
public function render(): string
{
$wrapperClass = $this->attributes['class'];
unset($this->attributes['class']);
$this->attributes['class'] = 'form-switch';
$checkbox = form_checkbox($this->attributes, $this->attributes['value'], $this->checked);
$hint = $this->hint !== '' ? hint_tooltip(lang('Podcast.form.lock_hint'), 'ml-1') : '';
return <<<CODE_SAMPLE
<label class="relative inline-flex items-center {$wrapperClass}">
{$checkbox}
<span class="form-switch-slider"></span>
<span class="ml-2">{$this->label}{$hint}</span>
</label>
CODE_SAMPLE;
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace App\View\Components\Forms;
use ViewComponents\Component;
class XMLEditor extends Component
{
protected string $content = '';
/**
* @var array<string, string>
*/
protected array $attributes = [
'slot' => 'textarea',
'rows' => '5',
'class' => 'textarea',
];
public function render(): string
{
$textarea = form_textarea($this->attributes, $this->content);
return <<<CODE_SAMPLE
<xml-editor>{$textarea}</time-ago>
CODE_SAMPLE;
}
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace App\View\Components;
use ViewComponents\Component;
class Icon extends Component
{
public string $glyph = '';
public function render(): string
{
$svgContents = file_get_contents('assets/icons/' . $this->glyph . '.svg');
if ($svgContents) {
if ($this->attributes['class'] !== '') {
$svgContents = str_replace('<svg', '<svg class="' . $this->attributes['class'] . '"', $svgContents);
}
return $svgContents;
}
return '□';
}
}

View File

@ -16,7 +16,7 @@
]) ?> ]) ?>
<?= csrf_field() ?> <?= csrf_field() ?>
<?= form_label(lang('Contributor.form.user'), 'user') ?> <?= component('Forms/Label', ['text' => lang('Contributor.form.user')], ['for' => 'user']) ?>
<?= form_dropdown('user', $userOptions, [old('user', '')], [ <?= form_dropdown('user', $userOptions, [old('user', '')], [
'id' => 'user', 'id' => 'user',
'class' => 'form-select mb-4', 'class' => 'form-select mb-4',
@ -24,7 +24,7 @@
'placeholder' => lang('Contributor.form.user_placeholder') 'placeholder' => lang('Contributor.form.user_placeholder')
]) ?> ]) ?>
<?= form_label(lang('Contributor.form.role'), 'role') ?> <?= component('Forms/Label', ['text' => lang('Contributor.form.role')], ['for' => 'role']) ?>
<?= form_dropdown('role', $roleOptions, [old('role', '')], [ <?= form_dropdown('role', $roleOptions, [old('role', '')], [
'id' => 'role', 'id' => 'role',
'class' => 'form-select mb-4', 'class' => 'form-select mb-4',

View File

@ -16,7 +16,7 @@
]) ?> ]) ?>
<?= csrf_field() ?> <?= csrf_field() ?>
<?= form_label(lang('Contributor.form.role'), 'role') ?> <?= component('Forms/Label', ['text' => lang('Contributor.form.role')], ['for' => 'role']) ?>
<?= form_dropdown('role', $roleOptions, [old('role', $contributorGroupId)], [ <?= form_dropdown('role', $roleOptions, [old('role', $contributorGroupId)], [
'id' => 'role', 'id' => 'role',
'class' => 'form-select mb-4', 'class' => 'form-select mb-4',

View File

@ -28,11 +28,10 @@
lang('Episode.form.info_section_subtitle'), lang('Episode.form.info_section_subtitle'),
) ?> ) ?>
<?= form_label( <?= component(
lang('Episode.form.audio_file'), 'Forms/Label',
'audio_file', ['text' => lang('Episode.form.audio_file'), 'hint' => lang('Episode.form.audio_file_hint')],
[], ['for' => 'audio_file'],
lang('Episode.form.audio_file_hint'),
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'audio_file', 'id' => 'audio_file',
@ -43,12 +42,14 @@
'accept' => '.mp3,.m4a', 'accept' => '.mp3,.m4a',
]) ?> ]) ?>
<?= form_label( <?= component(
lang('Episode.form.image'), 'Forms/Label',
'image', [
[], 'text' => lang('Episode.form.image'),
lang('Episode.form.image_hint'), 'hint' => lang('Episode.form.image_hint'),
true, 'isOptional' => true
],
['for' => 'image'],
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'image', 'id' => 'image',
@ -61,11 +62,11 @@
'Common.forms.image_size_hint', 'Common.forms.image_size_hint',
) ?></small> ) ?></small>
<?= form_label( <?= component(
lang('Episode.form.title'), 'Forms/Label',
'title', ['text' => lang('Episode.form.title'),
[], 'hint' => lang('Episode.form.title_hint')],
lang('Episode.form.title_hint'), ['for' => 'title'],
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'title', 'id' => 'title',
@ -76,10 +77,10 @@
'data-slugify' => 'title', 'data-slugify' => 'title',
]) ?> ]) ?>
<?= form_label( <?= component(
lang('Episode.form.permalink'), 'Forms/Label',
'slug', ['text' => lang('Episode.form.permalink')],
[], ['for' => 'slug']
) ?> ) ?>
<permalink-edit class="inline-flex items-center mb-4 text-xs" edit-label="<?= lang('Common.edit') ?>" copy-label="<?= lang('Common.copy') ?>" copied-label="<?= lang('Common.copied') ?>"> <permalink-edit class="inline-flex items-center mb-4 text-xs" edit-label="<?= lang('Common.edit') ?>" copy-label="<?= lang('Common.copy') ?>" copied-label="<?= lang('Common.copied') ?>">
<span slot="domain"><?= base_url('/@'. $podcast->handle . '/episodes' ) . '/' ?></span> <span slot="domain"><?= base_url('/@'. $podcast->handle . '/episodes' ) . '/' ?></span>
@ -96,7 +97,7 @@
<div class="flex flex-col mb-4 gap-x-2 gap-y-4 md:flex-row"> <div class="flex flex-col mb-4 gap-x-2 gap-y-4 md:flex-row">
<div class="flex flex-col flex-1"> <div class="flex flex-col flex-1">
<?= form_label(lang('Episode.form.season_number'), 'season_number') ?> <?= component('Forms/Label', ['text' => lang('Episode.form.season_number')], ['for' => 'season_number']) ?>
<?= form_input([ <?= form_input([
'id' => 'season_number', 'id' => 'season_number',
'name' => 'season_number', 'name' => 'season_number',
@ -106,7 +107,7 @@
]) ?> ]) ?>
</div> </div>
<div class="flex flex-col flex-1"> <div class="flex flex-col flex-1">
<?= form_label(lang('Episode.form.episode_number'), 'episode_number') ?> <?= component('Forms/Label', ['text' => lang('Episode.form.episode_number')], ['for' => 'episode_number']) ?>
<?= form_input([ <?= form_input([
'id' => 'episode_number', 'id' => 'episode_number',
'name' => 'episode_number', 'name' => 'episode_number',
@ -203,36 +204,45 @@
) ?> ) ?>
<div class="mb-4"> <div class="mb-4">
<?= form_label(lang('Episode.form.description'), 'description') ?> <?= component('Forms/Label', ['text' => lang('Episode.form.description')], ['for' => 'description']) ?>
<?= form_markdown_editor( <?= component(
'Forms/MarkdownEditor',
[
'content' => old('description', '', false),
],
[ [
'id' => 'description', 'id' => 'description',
'name' => 'description', 'name' => 'description',
'required' => 'required', 'required' => 'required',
], ],
old('description', '', false),
) ?> ) ?>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<?= form_label( <?= component( 'Forms/Label',
lang('Episode.form.description_footer'), [
'description_footer', 'text' => lang('Episode.form.description_footer'),
[], 'hint' => lang('Episode.form.description_footer_hint'),
lang('Episode.form.description_footer_hint'), 'isOptional' => true
true ],
[
'for' => 'description_footer'
],
) ?> ) ?>
<?= form_markdown_editor( <?= component(
'Forms/MarkdownEditor',
[
'content' => old(
'description_footer',
$podcast->episode_description_footer_markdown ?? '',
false,
),
],
[ [
'id' => 'description_footer', 'id' => 'description_footer',
'name' => 'description_footer', 'name' => 'description_footer',
'rows' => 6 'rows' => 6
], ],
old(
'description_footer',
$podcast->episode_description_footer_markdown ?? '',
false,
),
) ?> ) ?>
</div> </div>
@ -243,12 +253,13 @@
lang('Episode.form.location_section_subtitle'), lang('Episode.form.location_section_subtitle'),
) ?> ) ?>
<?= form_label( <?= component( 'Forms/Label',
lang('Episode.form.location_name'), [
'location_name', 'text' => lang('Episode.form.location_name'),
[], 'hint' => lang('Episode.form.location_name_hint'),
lang('Episode.form.location_name_hint'), 'isOptional' => true
true, ],
['for' => 'location_name'],
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'location_name', 'id' => 'location_name',
@ -290,12 +301,12 @@
<div class="py-2 tab-panels"> <div class="py-2 tab-panels">
<section id="transcript-file-upload" class="flex items-center tab-panel"> <section id="transcript-file-upload" class="flex items-center tab-panel">
<?= form_label( <?= component( 'Forms/Label',
lang('Episode.form.transcript_file'), [
'transcript_file', 'text' => lang('Episode.form.transcript_file'),
['class' => 'sr-only'], 'isOptional' => true
lang('Episode.form.transcript_file'), ],
true, ['for' => 'transcript_file', 'class' => 'sr-only'],
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'transcript_file', 'id' => 'transcript_file',
@ -306,12 +317,12 @@
]) ?> ]) ?>
</section> </section>
<section id="transcript-file-remote-url" class="tab-panel"> <section id="transcript-file-remote-url" class="tab-panel">
<?= form_label( <?= component( 'Forms/Label',
lang('Episode.form.transcript_file_remote_url'), [
'transcript_file_remote_url', 'text' => lang('Episode.form.transcript_file_remote_url'),
['class' => 'sr-only'], 'isOptional' => true
lang('Episode.form.transcript_file_remote_url'), ],
true, ['for' => 'transcript_file_remote_url', 'class' => 'sr-only'],
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'transcript_file_remote_url', 'id' => 'transcript_file_remote_url',
@ -353,12 +364,12 @@
<div class="py-2 tab-panels"> <div class="py-2 tab-panels">
<section id="chapters-file-upload" class="flex items-center tab-panel"> <section id="chapters-file-upload" class="flex items-center tab-panel">
<?= form_label( <?= component( 'Forms/Label',
lang('Episode.form.chapters_file'), [
'chapters_file', 'text' => lang('Episode.form.chapters_file'),
['class' => 'sr-only'], 'isOptional' => true
lang('Episode.form.chapters_file'), ],
true, ['for' => 'chapters_file', 'class' => 'sr-only'],
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'chapters_file', 'id' => 'chapters_file',
@ -369,12 +380,15 @@
]) ?> ]) ?>
</section> </section>
<section id="chapters-file-remote-url" class="tab-panel"> <section id="chapters-file-remote-url" class="tab-panel">
<?= form_label( <?= component( 'Forms/Label',
lang('Episode.form.chapters_file_remote_url'), [
'chapters_file_remote_url', 'text' => lang('Episode.form.chapters_file_remote_url'),
['class' => 'sr-only'], 'isOptional' => true
lang('Episode.form.chapters_file_remote_url'), ],
true, [
'for' => 'chapters_file_remote_url',
'class' => 'sr-only'
],
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'chapters_file_remote_url', 'id' => 'chapters_file_remote_url',
@ -395,27 +409,37 @@
lang('Episode.form.advanced_section_title'), lang('Episode.form.advanced_section_title'),
lang('Episode.form.advanced_section_subtitle'), lang('Episode.form.advanced_section_subtitle'),
) ?> ) ?>
<?= form_label( <?= component( 'Forms/Label',
lang('Episode.form.custom_rss'), [
'custom_rss', 'text' => lang('Episode.form.custom_rss'),
[], 'hint' => lang('Episode.form.custom_rss_hint'),
lang('Episode.form.custom_rss_hint'), 'isOptional' => true
true, ],
['for' => 'custom_rss']
) ?>
<?= component('Forms/XMLEditor',
[
'content' => old('custom_rss', '')
],
[
'id' => 'custom_rss',
'name' => 'custom_rss',
]
) ?> ) ?>
<?= xml_editor([
'id' => 'custom_rss',
'name' => 'custom_rss',
'class' => 'form-textarea',
'value' => old('custom_rss'),
]) ?>
<?= form_section_close() ?> <?= form_section_close() ?>
<?= form_switch( <?= component(
lang('Episode.form.block') . 'Forms/Toggler',
hint_tooltip(lang('Episode.form.block_hint'), 'ml-1'), [
['id' => 'block', 'name' => 'block'], 'label' => lang('Episode.form.block'),
'yes', 'hint' => lang('Episode.form.block_hint')
old('block', false), ],
[
'id' => 'block',
'name' => 'block',
'value' => 'yes',
'checked' => old('block', false),
]
) ?> ) ?>
<?= button( <?= button(

View File

@ -18,8 +18,8 @@
<?= csrf_field() ?> <?= csrf_field() ?>
<div class="inline-flex w-full p-2 mb-4 text-sm font-semibold text-yellow-800 bg-red-100 border border-red-300 rounded" role="alert"> <div class="inline-flex w-full p-2 mb-4 text-sm font-semibold text-yellow-800 bg-red-100 border border-red-300 rounded" role="alert">
<?= icon('alert', 'mr-2 text-lg flex-shrink-0') . <?= icon('alert', 'mr-2 text-lg flex-shrink-0') .
lang('Episode.form.warning') ?> lang('Episode.form.warning') ?>
</div> </div>
<?= form_section( <?= form_section(
@ -35,11 +35,11 @@
/>', />',
) ?> ) ?>
<?= form_label( <?= component(
lang('Episode.form.audio_file'), 'Forms/Label',
'audio_file', ['text' =>
[], lang('Episode.form.audio_file'), 'hint' => lang('Episode.form.audio_file_hint'),],
lang('Episode.form.audio_file_hint'), ['for' => 'audio_file'],
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'audio_file', 'id' => 'audio_file',
@ -49,12 +49,12 @@
'accept' => '.mp3,.m4a', 'accept' => '.mp3,.m4a',
]) ?> ]) ?>
<?= form_label( <?= component(
lang('Episode.form.image'), 'Forms/Label',
'image', ['text' =>
[], lang('Episode.form.image'), 'hint' => lang('Episode.form.image_hint'), 'isOptional' => true,],
lang('Episode.form.image_hint'), ['for' =>
true, 'image',]
) ?> ) ?>
<?= form_input([ <?= form_input([
@ -68,11 +68,12 @@
'Common.forms.image_size_hint', 'Common.forms.image_size_hint',
) ?></small> ) ?></small>
<?= form_label( <?= component(
lang('Episode.form.title'), 'Forms/Label',
'title', ['text' =>
[], lang('Episode.form.title'), 'hint' => lang('Episode.form.title_hint'),],
lang('Episode.form.title_hint'), ['for' =>
'title',]
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'title', 'id' => 'title',
@ -83,27 +84,29 @@
'data-slugify' => 'title', 'data-slugify' => 'title',
]) ?> ]) ?>
<?= form_label( <?= component(
lang('Episode.form.permalink'), 'Forms/Label',
'slug', [
[], 'text' => lang('Episode.form.permalink')
],
['for' => 'slug',]
) ?> ) ?>
<permalink-edit class="inline-flex items-center mb-4 text-xs" edit-label="<?= lang('Common.edit') ?>" copy-label="<?= lang('Common.copy') ?>" copied-label="<?= lang('Common.copied') ?>"> <permalink-edit class="inline-flex items-center mb-4 text-xs" edit-label="<?= lang('Common.edit') ?>" copy-label="<?= lang('Common.copy') ?>" copied-label="<?= lang('Common.copied') ?>">
<span slot="domain"><?= base_url('/@'. $podcast->handle . '/episodes' ) . '/' ?></span> <span slot="domain"><?= base_url('/@' . $podcast->handle . '/episodes') . '/' ?></span>
<?= form_input([ <?= form_input([
'id' => 'slug', 'id' => 'slug',
'name' => 'slug', 'name' => 'slug',
'class' => 'form-input flex-1 w-0 text-xs', 'class' => 'form-input flex-1 w-0 text-xs',
'value' => old('slug', $episode->slug), 'value' => old('slug', $episode->slug),
'required' => 'required', 'required' => 'required',
'data-slugify' => 'slug', 'data-slugify' => 'slug',
'slot' => 'slug-input' 'slot' => 'slug-input'
]) ?> ]) ?>
</permalink-edit> </permalink-edit>
<div class="flex flex-col mb-4 gap-x-2 gap-y-4 md:flex-row"> <div class="flex flex-col mb-4 gap-x-2 gap-y-4 md:flex-row">
<div class="flex flex-col flex-1"> <div class="flex flex-col flex-1">
<?= form_label(lang('Episode.form.season_number'), 'season_number') ?> <?= component('Forms/Label', ['text' => lang('Episode.form.season_number')], ['for' => 'season_number']) ?>
<?= form_input([ <?= form_input([
'id' => 'season_number', 'id' => 'season_number',
'name' => 'season_number', 'name' => 'season_number',
@ -113,7 +116,7 @@
]) ?> ]) ?>
</div> </div>
<div class="flex flex-col flex-1"> <div class="flex flex-col flex-1">
<?= form_label(lang('Episode.form.episode_number'), 'episode_number') ?> <?= component('Forms/Label', ['text' => lang('Episode.form.episode_number')], ['for' => 'episode_number']) ?>
<?= form_input([ <?= form_input([
'id' => 'episode_number', 'id' => 'episode_number',
'name' => 'episode_number', 'name' => 'episode_number',
@ -125,83 +128,83 @@
</div> </div>
<?= form_fieldset('', ['class' => 'flex mb-4 gap-1']) ?> <?= form_fieldset('', ['class' => 'flex mb-4 gap-1']) ?>
<legend> <legend>
<?= lang('Episode.form.type.label') . <?= lang('Episode.form.type.label') .
hint_tooltip(lang('Episode.form.type.hint'), 'ml-1') ?> hint_tooltip(lang('Episode.form.type.hint'), 'ml-1') ?>
</legend> </legend>
<?= form_radio( <?= form_radio(
['id' => 'full', 'name' => 'type', 'class' => 'form-radio-btn'], ['id' => 'full', 'name' => 'type', 'class' => 'form-radio-btn'],
'full', 'full',
old('type') ? old('type') === 'full' : $episode->type === 'full', old('type') ? old('type') === 'full' : $episode->type === 'full',
) ?> ) ?>
<label for="full" class="inline-flex items-center"> <label for="full" class="inline-flex items-center">
<?= lang('Episode.form.type.full') ?> <?= lang('Episode.form.type.full') ?>
</label> </label>
<?= form_radio( <?= form_radio(
['id' => 'trailer', 'name' => 'type', 'class' => 'form-radio-btn'], ['id' => 'trailer', 'name' => 'type', 'class' => 'form-radio-btn'],
'trailer', 'trailer',
old('type') ? old('type') === 'trailer' : $episode->type === 'trailer', old('type') ? old('type') === 'trailer' : $episode->type === 'trailer',
) ?> ) ?>
<label for="trailer" class="inline-flex items-center"> <label for="trailer" class="inline-flex items-center">
<?= lang('Episode.form.type.trailer') ?> <?= lang('Episode.form.type.trailer') ?>
</label> </label>
<?= form_radio( <?= form_radio(
['id' => 'bonus', 'name' => 'type', 'class' => 'form-radio-btn'], ['id' => 'bonus', 'name' => 'type', 'class' => 'form-radio-btn'],
'bonus', 'bonus',
old('type') ? old('type') === 'bonus' : $episode->type === 'bonus', old('type') ? old('type') === 'bonus' : $episode->type === 'bonus',
) ?> ) ?>
<label for="bonus" class="inline-flex items-center"> <label for="bonus" class="inline-flex items-center">
<?= lang('Episode.form.type.bonus') ?> <?= lang('Episode.form.type.bonus') ?>
</label> </label>
<?= form_fieldset_close() ?> <?= form_fieldset_close() ?>
<?= form_fieldset('', ['class' => 'mb-6']) ?> <?= form_fieldset('', ['class' => 'mb-6']) ?>
<legend> <legend>
<?= lang('Episode.form.parental_advisory.label') . <?= lang('Episode.form.parental_advisory.label') .
hint_tooltip(lang('Episode.form.parental_advisory.hint'), 'ml-1') ?> hint_tooltip(lang('Episode.form.parental_advisory.hint'), 'ml-1') ?>
</legend> </legend>
<?= form_radio( <?= form_radio(
[ [
'id' => 'undefined', 'id' => 'undefined',
'name' => 'parental_advisory', 'name' => 'parental_advisory',
'class' => 'form-radio-btn', 'class' => 'form-radio-btn',
], ],
'undefined', 'undefined',
old('parental_advisory') old('parental_advisory')
? old('parental_advisory') === 'undefined' ? old('parental_advisory') === 'undefined'
: $episode->parental_advisory === null, : $episode->parental_advisory === null,
) ?> ) ?>
<label for="undefined"><?= lang( <label for="undefined"><?= lang(
'Episode.form.parental_advisory.undefined', 'Episode.form.parental_advisory.undefined',
) ?></label> ) ?></label>
<?= form_radio( <?= form_radio(
[ [
'id' => 'clean', 'id' => 'clean',
'name' => 'parental_advisory', 'name' => 'parental_advisory',
'class' => 'form-radio-btn', 'class' => 'form-radio-btn',
], ],
'clean', 'clean',
old('parental_advisory') old('parental_advisory')
? old('parental_advisory') === 'clean' ? old('parental_advisory') === 'clean'
: $episode->parental_advisory === 'clean', : $episode->parental_advisory === 'clean',
) ?> ) ?>
<label for="clean"><?= lang( <label for="clean"><?= lang(
'Episode.form.parental_advisory.clean', 'Episode.form.parental_advisory.clean',
) ?></label> ) ?></label>
<?= form_radio( <?= form_radio(
[ [
'id' => 'explicit', 'id' => 'explicit',
'name' => 'parental_advisory', 'name' => 'parental_advisory',
'class' => 'form-radio-btn', 'class' => 'form-radio-btn',
], ],
'explicit', 'explicit',
old('parental_advisory') old('parental_advisory')
? old('parental_advisory') === 'explicit' ? old('parental_advisory') === 'explicit'
: $episode->parental_advisory === 'explicit', : $episode->parental_advisory === 'explicit',
) ?> ) ?>
<label for="explicit"><?= lang( <label for="explicit"><?= lang(
'Episode.form.parental_advisory.explicit', 'Episode.form.parental_advisory.explicit',
) ?></label> ) ?></label>
<?= form_fieldset_close() ?> <?= form_fieldset_close() ?>
<?= form_section_close() ?> <?= form_section_close() ?>
@ -213,36 +216,43 @@
) ?> ) ?>
<div class="mb-4"> <div class="mb-4">
<?= form_label(lang('Episode.form.description'), 'description') ?> <?= component('Forms/Label', ['text' => lang('Episode.form.description')], ['for' => 'description']) ?>
<?= form_markdown_editor( <?= component(
'Forms/MarkdownEditor',
[
'content' => old('description', $episode->description_markdown, false),
],
[ [
'id' => 'description', 'id' => 'description',
'name' => 'description', 'name' => 'description',
'required' => 'required', 'required' => 'required',
], ],
old('description', $episode->description_markdown, false),
) ?> ) ?>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<?= form_label( <?= component('Forms/Label',
lang('Episode.form.description_footer'), [
'description_footer', 'text' => lang('Episode.form.description_footer'),
[], 'hint' => lang('Episode.form.description_footer_hint'),
lang('Episode.form.description_footer_hint'), 'isOptional' => true
true ],
['for' => 'description_footer'],
) ?> ) ?>
<?= form_markdown_editor( <?= component(
'Forms/MarkdownEditor',
[
'content' => old(
'description_footer',
$podcast->episode_description_footer_markdown ?? '',
false,
),
],
[ [
'id' => 'description_footer', 'id' => 'description_footer',
'name' => 'description_footer', 'name' => 'description_footer',
'rows' => 6 'rows' => 6
], ],
old(
'description_footer',
$podcast->episode_description_footer_markdown ?? '',
false,
),
) ?> ) ?>
</div> </div>
@ -253,12 +263,13 @@
lang('Episode.form.location_section_subtitle'), lang('Episode.form.location_section_subtitle'),
) ?> ) ?>
<?= form_label( <?= component('Forms/Label',
lang('Episode.form.location_name'), [
'location_name', 'text' => lang('Episode.form.location_name'),
[], 'hint' => lang('Episode.form.location_name_hint'),
lang('Episode.form.location_name_hint'), 'isOptional' => true
true, ],
['for' => 'location_name']
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'location_name', 'id' => 'location_name',
@ -273,34 +284,34 @@
lang('Episode.form.additional_files_section_title'), lang('Episode.form.additional_files_section_title'),
lang('Episode.form.additional_files_section_subtitle', [ lang('Episode.form.additional_files_section_subtitle', [
'podcastNamespaceLink' => 'podcastNamespaceLink' =>
'“<a href="https://github.com/Podcastindex-org/podcast-namespace" target="_blank" rel="noreferrer noopener" style="text-decoration: underline;">podcast namespace</a>”', '“<a href="https://github.com/Podcastindex-org/podcast-namespace" target="_blank" rel="noreferrer noopener" style="text-decoration: underline;">podcast namespace</a>”',
]), ]),
) ?> ) ?>
<?= form_fieldset('', ['class' => 'flex flex-col mb-4']) ?> <?= form_fieldset('', ['class' => 'flex flex-col mb-4']) ?>
<legend><?= lang('Episode.form.transcript') . <legend><?= lang('Episode.form.transcript') .
'<small class="ml-1 lowercase">(' . '<small class="ml-1 lowercase">(' .
lang('Common.optional') . lang('Common.optional') .
')</small>' . ')</small>' .
hint_tooltip(lang('Episode.form.transcript_hint'), 'ml-1') ?></legend> hint_tooltip(lang('Episode.form.transcript_hint'), 'ml-1') ?></legend>
<div class="mb-4 form-input-tabs"> <div class="mb-4 form-input-tabs">
<input type="radio" name="transcript-choice" id="transcript-file-upload-choice" aria-controls="transcript-file-upload-choice" value="upload-file" <?= $episode->transcript_file_remote_url <input type="radio" name="transcript-choice" id="transcript-file-upload-choice" aria-controls="transcript-file-upload-choice" value="upload-file" <?= $episode->transcript_file_remote_url
? '' ? ''
: 'checked' ?> /> : 'checked' ?> />
<label for="transcript-file-upload-choice"><?= lang( <label for="transcript-file-upload-choice"><?= lang(
'Common.forms.upload_file', 'Common.forms.upload_file',
) ?></label> ) ?></label>
<input type="radio" name="transcript-choice" id="transcript-file-remote-url-choice" aria-controls="transcript-file-remote-url-choice" value="remote-url" <?= $episode->transcript_file_remote_url <input type="radio" name="transcript-choice" id="transcript-file-remote-url-choice" aria-controls="transcript-file-remote-url-choice" value="remote-url" <?= $episode->transcript_file_remote_url
? 'checked' ? 'checked'
: '' ?> /> : '' ?> />
<label for="transcript-file-remote-url-choice"><?= lang( <label for="transcript-file-remote-url-choice"><?= lang(
'Common.forms.remote_url', 'Common.forms.remote_url',
) ?></label> ) ?></label>
<div class="py-2 tab-panels"> <div class="py-2 tab-panels">
<section id="transcript-file-upload" class="flex items-center tab-panel"> <section id="transcript-file-upload" class="flex items-center tab-panel">
<?php if ($episode->transcript_file): ?> <?php if ($episode->transcript_file) : ?>
<div class="flex justify-between"> <div class="flex justify-between">
<?= anchor( <?= anchor(
$episode->transcript_file_url, $episode->transcript_file_url,
@ -321,7 +332,7 @@
icon('delete-bin', 'mx-auto'), icon('delete-bin', 'mx-auto'),
[ [
'class' => 'class' =>
'p-1 bg-red-200 rounded-full text-red-700 hover:text-red-900', 'p-1 bg-red-200 rounded-full text-red-700 hover:text-red-900',
'data-toggle' => 'tooltip', 'data-toggle' => 'tooltip',
'data-placement' => 'bottom', 'data-placement' => 'bottom',
'title' => lang( 'title' => lang(
@ -331,12 +342,12 @@
) ?> ) ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?= form_label( <?= component( 'Forms/Label',
lang('Episode.form.transcript_file'), [
'transcript_file', 'text' => lang('Episode.form.transcript_file'),
['class' => 'sr-only'], 'isOptional' => true
lang('Episode.form.transcript_file'), ],
true, ['for' => 'transcript_file', 'class' => 'sr-only'],
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'transcript_file', 'id' => 'transcript_file',
@ -345,14 +356,14 @@
'type' => 'file', 'type' => 'file',
'accept' => '.txt,.html,.srt,.json', 'accept' => '.txt,.html,.srt,.json',
]) ?> ]) ?>
</section> </section>
<section id="transcript-file-remote-url" class="tab-panel"> <section id="transcript-file-remote-url" class="tab-panel">
<?= form_label( <?= component( 'Forms/Label',
lang('Episode.form.transcript_file_remote_url'), [
'transcript_file_remote_url', 'text' => lang('Episode.form.transcript_file_remote_url'),
['class' => 'sr-only'], 'isOptional' => true
lang('Episode.form.transcript_file_remote_url'), ],
true, ['for' => 'transcript_file_remote_url', 'class' => 'sr-only'],
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'transcript_file_remote_url', 'id' => 'transcript_file_remote_url',
@ -365,35 +376,35 @@
$episode->transcript_file_remote_url, $episode->transcript_file_remote_url,
), ),
]) ?> ]) ?>
</section> </section>
</div>
</div> </div>
</div>
<?= form_fieldset_close() ?> <?= form_fieldset_close() ?>
<?= form_fieldset('', ['class' => 'flex flex-col mb-4']) ?> <?= form_fieldset('', ['class' => 'flex flex-col mb-4']) ?>
<legend><?= lang('Episode.form.chapters') . <legend><?= lang('Episode.form.chapters') .
'<small class="ml-1 lowercase">(' . '<small class="ml-1 lowercase">(' .
lang('Common.optional') . lang('Common.optional') .
')</small>' . ')</small>' .
hint_tooltip(lang('Episode.form.chapters_hint'), 'ml-1') ?></legend> hint_tooltip(lang('Episode.form.chapters_hint'), 'ml-1') ?></legend>
<div class="mb-4 form-input-tabs"> <div class="mb-4 form-input-tabs">
<input type="radio" name="chapters-choice" id="chapters-file-upload-choice" aria-controls="chapters-file-upload-choice" value="upload-file" <?= $episode->chapters_file_remote_url <input type="radio" name="chapters-choice" id="chapters-file-upload-choice" aria-controls="chapters-file-upload-choice" value="upload-file" <?= $episode->chapters_file_remote_url
? '' ? ''
: 'checked' ?> /> : 'checked' ?> />
<label for="chapters-file-upload-choice"><?= lang( <label for="chapters-file-upload-choice"><?= lang(
'Common.forms.upload_file', 'Common.forms.upload_file',
) ?></label> ) ?></label>
<input type="radio" name="chapters-choice" id="chapters-file-remote-url-choice" aria-controls="chapters-file-remote-url-choice" value="remote-url" <?= $episode->chapters_file_remote_url <input type="radio" name="chapters-choice" id="chapters-file-remote-url-choice" aria-controls="chapters-file-remote-url-choice" value="remote-url" <?= $episode->chapters_file_remote_url
? 'checked' ? 'checked'
: '' ?> /> : '' ?> />
<label for="chapters-file-remote-url-choice"><?= lang( <label for="chapters-file-remote-url-choice"><?= lang(
'Common.forms.remote_url', 'Common.forms.remote_url',
) ?></label> ) ?></label>
<div class="py-2 tab-panels"> <div class="py-2 tab-panels">
<section id="chapters-file-upload" class="flex items-center tab-panel"> <section id="chapters-file-upload" class="flex items-center tab-panel">
<?php if ($episode->chapters_file): ?> <?php if ($episode->chapters_file) : ?>
<div class="flex justify-between"> <div class="flex justify-between">
<?= anchor( <?= anchor(
$episode->chapters_file_url, $episode->chapters_file_url,
@ -413,7 +424,7 @@
icon('delete-bin', 'mx-auto'), icon('delete-bin', 'mx-auto'),
[ [
'class' => 'class' =>
'p-1 bg-red-200 rounded-full text-red-700 hover:text-red-900', 'p-1 bg-red-200 rounded-full text-red-700 hover:text-red-900',
'data-toggle' => 'tooltip', 'data-toggle' => 'tooltip',
'data-placement' => 'bottom', 'data-placement' => 'bottom',
'title' => lang( 'title' => lang(
@ -423,12 +434,12 @@
) ?> ) ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?= form_label( <?= component( 'Forms/Label',
lang('Episode.form.chapters_file'), [
'chapters_file', 'text' => lang('Episode.form.chapters_file'),
['class' => 'sr-only'], 'isOptional' => true
lang('Episode.form.chapters_file'), ],
true, ['for' => 'chapters_file', 'class' => 'sr-only'],
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'chapters_file', 'id' => 'chapters_file',
@ -437,14 +448,17 @@
'type' => 'file', 'type' => 'file',
'accept' => '.json', 'accept' => '.json',
]) ?> ]) ?>
</section> </section>
<section id="chapters-file-remote-url" class="tab-panel"> <section id="chapters-file-remote-url" class="tab-panel">
<?= form_label( <?= component( 'Forms/Label',
lang('Episode.form.chapters_file_remote_url'), [
'chapters_file_remote_url', 'text' => lang('Episode.form.chapters_file_remote_url'),
['class' => 'sr-only'], 'isOptional' => true
lang('Episode.form.chapters_file_remote_url'), ],
true, [
'for' => 'chapters_file_remote_url',
'class' => 'sr-only'
],
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'chapters_file_remote_url', 'id' => 'chapters_file_remote_url',
@ -457,9 +471,9 @@
$episode->chapters_file_remote_url, $episode->chapters_file_remote_url,
), ),
]) ?> ]) ?>
</section> </section>
</div>
</div> </div>
</div>
<?= form_fieldset_close() ?> <?= form_fieldset_close() ?>
<?= form_section_close() ?> <?= form_section_close() ?>
@ -468,27 +482,39 @@
lang('Episode.form.advanced_section_title'), lang('Episode.form.advanced_section_title'),
lang('Episode.form.advanced_section_subtitle'), lang('Episode.form.advanced_section_subtitle'),
) ?> ) ?>
<?= form_label( <?= component('Forms/Label',
lang('Episode.form.custom_rss'), [
'custom_rss', 'text' => lang('Episode.form.custom_rss'),
[], 'hint' => lang('Episode.form.custom_rss_hint'),
lang('Episode.form.custom_rss_hint'), 'isOptional' => true,
true, ],
[
'for' => 'custom_rss',
]
) ?>
<?= component('Forms/XMLEditor',
[
'content' => old('custom_rss', $episode->custom_rss_string)
],
[
'id' => 'custom_rss',
'name' => 'custom_rss',
]
) ?> ) ?>
<?= xml_editor([
'id' => 'custom_rss',
'name' => 'custom_rss',
'class' => 'form-textarea',
'value' => old('custom_rss', $episode->custom_rss_string),
]) ?>
<?= form_section_close() ?> <?= form_section_close() ?>
<?= form_switch( <?= component(
lang('Episode.form.block') . 'Forms/Toggler',
hint_tooltip(lang('Episode.form.block_hint'), 'ml-1'), [
['id' => 'block', 'name' => 'block'], 'label' => lang('Episode.form.block'),
'yes', 'hint' => lang('Episode.form.block_hint')
old('block', $episode->is_blocked), ],
[
'id' => 'block',
'name' => 'block',
'value' => 'yes',
'checked' => old('block', $episode->is_blocked),
]
) ?> ) ?>
<?= button( <?= button(

View File

@ -10,7 +10,7 @@
<?= $this->section('content') ?> <?= $this->section('content') ?>
<?= form_label(lang('Episode.embeddable_player.label'), 'label') ?> <p><?= lang('Episode.embeddable_player.label') ?></p>
<div class="flex w-full mt-6 mb-6"> <div class="flex w-full mt-6 mb-6">
<?php foreach ($themes as $themeKey => $theme): ?> <?php foreach ($themes as $themeKey => $theme): ?>

View File

@ -60,8 +60,8 @@
($person->information_url === null ($person->information_url === null
? '' ? ''
: "<a href=\"{$person->information_url}\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"text-sm text-blue-800 hover:underline\">" . : "<a href=\"{$person->information_url}\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"text-sm text-blue-800 hover:underline\">" .
$person->information_url . $person->information_url .
'</a>') . '</a>') .
'</div></div>'; '</div></div>';
}, },
], ],
@ -95,40 +95,36 @@
lang('Person.episode_form.add_section_subtitle'), lang('Person.episode_form.add_section_subtitle'),
) ?> ) ?>
<?= form_label( <?= component(
lang('Person.episode_form.persons'), 'Forms/Label',
'persons', ['text' => lang('Person.episode_form.persons'), 'hint' => lang('Person.episode_form.persons_hint')],
[], ['for' => 'persons'],
lang('Person.episode_form.persons_hint'),
) ?> ) ?>
<?= form_multiselect('persons[]', $personOptions, old('persons', []), [ <?= component('Forms/MultiSelect', ['options' => $personOptions, 'selected' => old('persons', [])], [
'id' => 'persons', 'id' => 'persons',
'class' => 'form-select mb-4', 'name' => 'persons[]',
'class' => 'mb-4',
'required' => 'required', 'required' => 'required',
]) ?> ]) ?>
<?= form_label( <?= component(
lang('Person.episode_form.roles'), 'Forms/Label',
'roles', ['text' => lang('Person.episode_form.roles'), 'hint' => lang('Person.episode_form.roles_hint'), 'isOptional' => true],
[], ['for' => 'roles'],
lang('Person.episode_form.roles_hint'),
true,
) ?> ) ?>
<?= form_multiselect( <?= component('Forms/MultiSelect', ['options' => $taxonomyOptions, 'selected' => old('roles', [])], [
'roles[]', 'id' => 'roles',
$taxonomyOptions, 'name' => 'roles[]',
old('roles', []), 'class' => 'mb-4',
['id' => 'roles', 'class' => 'form-select mb-4'], ]) ?>
) ?>
<?= form_section_close() ?> <?= form_section_close() ?>
<?= button( <?= button(
lang('Person.episode_form.submit_add'), lang('Person.episode_form.submit_add'),
'', '',
['variant' => 'primary'], ['variant' => 'primary'],
['type' => 'submit', 'class' => 'self-end'], ['type' => 'submit', 'class' => 'self-end'],
) ?> ) ?>
<?= form_close() ?> <?= form_close() ?>
<?= $this->endSection() ?> <?= $this->endSection() ?>

View File

@ -8,7 +8,6 @@
<?= lang('Episode.publish_edit') ?> <?= lang('Episode.publish_edit') ?>
<?= $this->endSection() ?> <?= $this->endSection() ?>
<?= $this->section('content') ?> <?= $this->section('content') ?>
<?= anchor( <?= anchor(
@ -33,14 +32,11 @@
<small class="max-w-md mb-2 text-gray-600"><?= lang('Episode.publish_form.post_hint') ?></small> <small class="max-w-md mb-2 text-gray-600"><?= lang('Episode.publish_form.post_hint') ?></small>
<div class="mb-8 overflow-hidden bg-white shadow-md rounded-xl"> <div class="mb-8 overflow-hidden bg-white shadow-md rounded-xl">
<div class="flex px-4 py-3"> <div class="flex px-4 py-3">
<img src="<?= $podcast->actor->avatar_image_url ?>" alt="<?= $podcast->actor <img src="<?= $podcast->actor->avatar_image_url ?>" alt="<?= $podcast->actor->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
<div class="flex flex-col min-w-0"> <div class="flex flex-col min-w-0">
<p class="flex items-baseline min-w-0"> <p class="flex items-baseline min-w-0">
<span class="mr-2 font-semibold truncate"><?= $podcast->actor <span class="mr-2 font-semibold truncate"><?= $podcast->actor->display_name ?></span>
->display_name ?></span> <span class="text-sm text-gray-500 truncate">@<?= $podcast->actor->username ?></span>
<span class="text-sm text-gray-500 truncate">@<?= $podcast
->actor->username ?></span>
</p> </p>
<?= relative_time($post->published_at, 'text-xs text-gray-500') ?> <?= relative_time($post->published_at, 'text-xs text-gray-500') ?>
</div> </div>
@ -85,17 +81,17 @@
</div> </div>
<footer class="flex justify-around px-6 py-3"> <footer class="flex justify-around px-6 py-3">
<span class="inline-flex items-center"><?= icon( <span class="inline-flex items-center"><?= icon(
'chat', 'chat',
'text-xl mr-1 text-gray-400', 'text-xl mr-1 text-gray-400',
) . '0' ?></span> ) . '0' ?></span>
<span class="inline-flex items-center"><?= icon( <span class="inline-flex items-center"><?= icon(
'repeat', 'repeat',
'text-xl mr-1 text-gray-400', 'text-xl mr-1 text-gray-400',
) . '0' ?></span> ) . '0' ?></span>
<span class="inline-flex items-center"><?= icon( <span class="inline-flex items-center"><?= icon(
'heart', 'heart',
'text-xl mr-1 text-gray-400', 'text-xl mr-1 text-gray-400',
) . '0' ?></span> ) . '0' ?></span>
</footer> </footer>
</div> </div>
@ -151,10 +147,10 @@
'data-input' => '', 'data-input' => '',
]) ?> ]) ?>
<button class="p-3 border border-l-0 border-gray-500 bg-pine-100 focus:outline-none rounded-r-md hover:bg-pine-200 focus:ring" type="button" aria-label="<?= lang( <button class="p-3 border border-l-0 border-gray-500 bg-pine-100 focus:outline-none rounded-r-md hover:bg-pine-200 focus:ring" type="button" aria-label="<?= lang(
'Episode.publish_form.scheduled_publication_date_clear', 'Episode.publish_form.scheduled_publication_date_clear',
) ?>" title="<?= lang( ) ?>" title="<?= lang(
'Episode.publish_form.scheduled_publication_date_clear', 'Episode.publish_form.scheduled_publication_date_clear',
) ?>" data-clear=""><?= icon('close') ?></button> ) ?>" data-clear=""><?= icon('close') ?></button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -46,14 +46,17 @@
<div class="mb-4"> <div class="mb-4">
<?= form_label(lang('Page.form.content'), 'content') ?> <?= form_label(lang('Page.form.content'), 'content') ?>
<?= form_markdown_editor( <?= component(
'Forms/MarkdownEditor',
[
'content' => old('content', '', false)
],
[ [
'id' => 'content', 'id' => 'content',
'name' => 'content', 'name' => 'content',
'required' => 'required', 'required' => 'required',
'rows' => 20
], ],
old('content', '', false),
['rows' => '20']
) ?> ) ?>
</div> </div>

View File

@ -46,13 +46,16 @@
<div class="mb-4"> <div class="mb-4">
<?= form_label(lang('Page.form.content'), 'content') ?> <?= form_label(lang('Page.form.content'), 'content') ?>
<?= form_markdown_editor( <?= component(
'Forms/MarkdownEditor',
[
'content' => old('content', $page->content_markdown, false),
],
[ [
'id' => 'content', 'id' => 'content',
'name' => 'content', 'name' => 'content',
'required' => 'required', 'required' => 'required',
], ],
old('content', $page->content_markdown, false),
) ?> ) ?>
</div> </div>

View File

@ -82,13 +82,16 @@
<div class="mb-4"> <div class="mb-4">
<?= form_label(lang('Podcast.form.description'), 'description') ?> <?= form_label(lang('Podcast.form.description'), 'description') ?>
<?= form_markdown_editor( <?= component(
'Forms/MarkdownEditor',
[
'content' => old('description', '', false),
],
[ [
'id' => 'description', 'id' => 'description',
'name' => 'description', 'name' => 'description',
'required' => 'required', 'required' => 'required',
], ],
old('description', '', false),
) ?> ) ?>
</div> </div>
@ -122,16 +125,12 @@
'', '',
true, true,
) ?> ) ?>
<?= form_multiselect( <?= component('Forms/MultiSelect', ['options' => $categoryOptions, 'selected' => old('other_categories', [])], [
'other_categories[]', 'id' => 'other_categories',
$categoryOptions, 'name' => 'other_categories[]',
[old('other_categories', '')], 'class' => 'mb-4',
[ 'data-max-item-count' => '2',
'id' => 'other_categories', ]) ?>
'class' => 'mb-4',
'data-max-item-count' => '2',
],
) ?>
<?= form_fieldset('', ['class' => 'mb-4']) ?> <?= form_fieldset('', ['class' => 'mb-4']) ?>
<legend> <legend>
@ -340,12 +339,15 @@
lang('Podcast.form.custom_rss_hint'), lang('Podcast.form.custom_rss_hint'),
true, true,
) ?> ) ?>
<?= xml_editor([ <?= component('Forms/XMLEditor',
'id' => 'custom_rss', [
'name' => 'custom_rss', 'content' => old('custom_rss', '')
'class' => 'form-textarea', ],
'value' => old('custom_rss'), [
]) ?> 'id' => 'custom_rss',
'name' => 'custom_rss',
]
) ?>
<?= form_section_close() ?> <?= form_section_close() ?>
<?= form_section( <?= form_section(
@ -353,28 +355,46 @@
lang('Podcast.form.status_section_subtitle'), lang('Podcast.form.status_section_subtitle'),
) ?> ) ?>
<?= form_switch( <?= component(
lang('Podcast.form.block'), 'Forms/Toggler',
['id' => 'block', 'name' => 'block'], [
'yes', 'label' => lang('Podcast.form.lock'),
old('block', false), 'hint' => lang('Podcast.form.lock_hint'),
'mb-2', ],
[
'id' => 'lock',
'name' => 'lock',
'value' => 'yes',
'checked' => old('complete', true),
'class' => 'mb-2'
]
) ?> ) ?>
<?= form_switch( <?= component(
lang('Podcast.form.complete'), 'Forms/Toggler',
['id' => 'complete', 'name' => 'complete'], [
'yes', 'label' => lang('Podcast.form.block'),
old('complete', false), ],
'mb-2', [
'id' => 'block',
'name' => 'block',
'value' => 'yes',
'checked' => old('block', false),
'class' => 'mb-2'
]
) ?> ) ?>
<?= form_switch( <?= component(
lang('Podcast.form.lock') . 'Forms/Toggler',
hint_tooltip(lang('Podcast.form.lock_hint'), 'ml-1'), [
['id' => 'lock', 'name' => 'lock'], 'label' => lang('Podcast.form.complete'),
'yes', ],
old('lock', true), [
'id' => 'complete',
'name' => 'complete',
'value' => 'yes',
'checked' => old('complete', false),
]
) ?> ) ?>
<?= form_section_close() ?> <?= form_section_close() ?>

View File

@ -8,12 +8,14 @@
<?= lang('Podcast.edit') ?> <?= lang('Podcast.edit') ?>
<?= $this->endSection() ?> <?= $this->endSection() ?>
<?= $this->section('content') ?> <?= $this->section('content') ?>
<?= form_open_multipart(route_to('podcast-edit', $podcast->id), [ <?= form_open_multipart(route_to('podcast-edit', $podcast->id), [
'method' => 'post', 'method' => 'post',
'class' => 'flex flex-col', 'class' => 'flex flex-col',
]) ?> ]) ?>
<?= csrf_field() ?> <?= csrf_field() ?>
<?= form_section( <?= form_section(
@ -22,11 +24,8 @@
) ?> ) ?>
<?= form_label(lang('Podcast.form.image'), 'image') ?> <?= form_label(lang('Podcast.form.image'), 'image') ?>
<img
src="<?= $podcast->image->thumbnail_url ?>" <img src="<?= $podcast->image->thumbnail_url ?>" alt="<?= $podcast->title ?>" class="object-cover w-32 h-32" />
alt="<?= $podcast->title ?>"
class="object-cover w-32 h-32"
/>
<?= form_input([ <?= form_input([
'id' => 'image', 'id' => 'image',
'name' => 'image', 'name' => 'image',
@ -34,11 +33,14 @@
'type' => 'file', 'type' => 'file',
'accept' => '.jpg,.jpeg,.png', 'accept' => '.jpg,.jpeg,.png',
]) ?> ]) ?>
<small class="mb-4 text-gray-600"><?= lang( <small class="mb-4 text-gray-600"><?= lang(
'Common.forms.image_size_hint', 'Common.forms.image_size_hint',
) ?></small> ) ?></small>
<?= form_label(lang('Podcast.form.title'), 'title') ?> <?= form_label(lang('Podcast.form.title'), 'title') ?>
<?= form_input([ <?= form_input([
'id' => 'title', 'id' => 'title',
'name' => 'title', 'name' => 'title',
@ -46,46 +48,52 @@
'value' => old('title', $podcast->title), 'value' => old('title', $podcast->title),
'required' => 'required', 'required' => 'required',
]) ?> ]) ?>
<span class="mb-4 text-sm"><?= $podcast->link ?></span> <span class="mb-4 text-sm"><?= $podcast->link ?></span>
<?= form_fieldset('', ['class' => 'mb-4']) ?> <?= form_fieldset('', ['class' => 'mb-4']) ?>
<legend><?= lang('Podcast.form.type.label') .
hint_tooltip(lang('Podcast.form.type.hint'), 'ml-1') ?> <legend><?= lang('Podcast.form.type.label') .
</legend> hint_tooltip(lang('Podcast.form.type.hint'), 'ml-1') ?>
<?= form_radio( </legend>
['id' => 'episodic', 'name' => 'type', 'class' => 'form-radio-btn'], <?= form_radio(
'episodic', ['id' => 'episodic', 'name' => 'type', 'class' => 'form-radio-btn'],
old('type') ? old('type') == 'episodic' : $podcast->type == 'episodic', 'episodic',
) ?> old('type') ? old('type') == 'episodic' : $podcast->type == 'episodic',
<label for="episodic"><?= lang('Podcast.form.type.episodic') ?></label> ) ?>
<?= form_radio( <label for="episodic"><?= lang('Podcast.form.type.episodic') ?></label>
['id' => 'serial', 'name' => 'type', 'class' => 'form-radio-btn'], <?= form_radio(
'serial', ['id' => 'serial', 'name' => 'type', 'class' => 'form-radio-btn'],
old('type') ? old('type') == 'serial' : $podcast->type == 'serial', 'serial',
) ?> old('type') ? old('type') == 'serial' : $podcast->type == 'serial',
<label for="serial"><?= lang('Podcast.form.type.serial') ?></label> ) ?>
<label for="serial"><?= lang('Podcast.form.type.serial') ?></label>
<?= form_fieldset_close() ?> <?= form_fieldset_close() ?>
<div class="mb-4"> <div class="mb-4">
<?= form_label(lang('Podcast.form.description'), 'description') ?> <?= form_label(lang('Podcast.form.description'), 'description') ?>
<?= form_markdown_editor([ <?= component(
'Forms/MarkdownEditor',
[
'content' => old('description', $podcast->description_markdown, false)
],
[
'id' => 'description', 'id' => 'description',
'name' => 'description', 'name' => 'description',
'required' => 'required', 'required' => 'required',
], ],
old('description', $podcast->description_markdown, false)
) ?> ) ?>
</div> </div>
<?= form_section_close() ?> <?= form_section_close() ?>
<?= form_section( <?= form_section(
lang('Podcast.form.classification_section_title'), lang('Podcast.form.classification_section_title'),
lang('Podcast.form.classification_section_subtitle'), lang('Podcast.form.classification_section_subtitle'),
) ?> ) ?>
<?= form_label(lang('Podcast.form.language'), 'language') ?> <?= form_label(lang('Podcast.form.language'), 'language') ?>
<?= form_dropdown( <?= form_dropdown(
'language', 'language',
$languageOptions, $languageOptions,
@ -101,7 +109,7 @@
<?= form_dropdown( <?= form_dropdown(
'category', 'category',
$categoryOptions, $categoryOptions,
[old('category', (string) $podcast->category_id)], [old('category', $podcast->category_id)],
[ [
'id' => 'category', 'id' => 'category',
'class' => 'form-select mb-4', 'class' => 'form-select mb-4',
@ -116,65 +124,63 @@
'', '',
true, true,
) ?> ) ?>
<?= form_multiselect(
'other_categories[]', <?= component('Forms/MultiSelect', ['options' => $categoryOptions, 'selected' => old('other_categories', $podcast->other_categories_ids)], [
$categoryOptions, 'id' => 'other_categories',
old('other_categories', $podcast->other_categories_ids), 'name' => 'other_categories[]',
[ 'class' => 'mb-4',
'id' => 'other_categories', 'data-max-item-count' => '2',
'class' => 'mb-4', ]) ?>
'data-max-item-count' => '2',
],
) ?>
<?= form_fieldset('', ['class' => 'mb-4']) ?> <?= form_fieldset('', ['class' => 'mb-4']) ?>
<legend><?= lang('Podcast.form.parental_advisory.label') . <legend><?= lang('Podcast.form.parental_advisory.label') .
hint_tooltip(lang('Podcast.form.parental_advisory.hint'), 'ml-1') ?> hint_tooltip(lang('Podcast.form.parental_advisory.hint'), 'ml-1') ?></legend>
</legend> <?= form_radio(
<?= form_radio( [
[ 'id' => 'undefined',
'id' => 'undefined', 'name' => 'parental_advisory',
'name' => 'parental_advisory', 'class' => 'form-radio-btn',
'class' => 'form-radio-btn', ],
], 'undefined',
'undefined', old('parental_advisory')
old('parental_advisory') ? old('parental_advisory') === 'undefined'
? old('parental_advisory') === 'undefined' : $podcast->parental_advisory === null,
: $podcast->parental_advisory === null, ) ?>
) ?>
<label for="undefined"><?= lang(
'Podcast.form.parental_advisory.undefined',
) ?></label>
<?= form_radio(
[
'id' => 'clean',
'name' => 'parental_advisory',
'class' => 'form-radio-btn',
],
'clean',
old('parental_advisory')
? old('parental_advisory') === 'clean'
: $podcast->parental_advisory === 'clean',
) ?>
<label for="clean"><?= lang(
'Podcast.form.parental_advisory.clean',
) ?></label>
<?= form_radio(
[
'id' => 'explicit',
'name' => 'parental_advisory',
'class' => 'form-radio-btn',
],
'explicit',
old('parental_advisory')
? old('parental_advisory') === 'explicit'
: $podcast->parental_advisory === 'explicit',
) ?>
<label for="explicit"><?= lang(
'Podcast.form.parental_advisory.explicit',
) ?></label>
<?= form_fieldset_close() ?>
<label for="undefined"><?= lang(
'Podcast.form.parental_advisory.undefined',
) ?></label>
<?= form_radio(
[
'id' => 'clean',
'name' => 'parental_advisory',
'class' => 'form-radio-btn',
],
'clean',
old('parental_advisory')
? old('parental_advisory') === 'clean'
: $podcast->parental_advisory === 'clean',
) ?>
<label for="clean"><?= lang(
'Podcast.form.parental_advisory.clean',
) ?></label>
<?= form_radio(
[
'id' => 'explicit',
'name' => 'parental_advisory',
'class' => 'form-radio-btn',
],
'explicit',
old('parental_advisory')
? old('parental_advisory') === 'explicit'
: $podcast->parental_advisory === 'explicit',
) ?>
<label for="explicit"><?= lang(
'Podcast.form.parental_advisory.explicit',
) ?></label>
<?= form_fieldset_close() ?>
<?= form_section_close() ?> <?= form_section_close() ?>
<?= form_section( <?= form_section(
@ -188,6 +194,7 @@
[], [],
lang('Podcast.form.owner_name_hint'), lang('Podcast.form.owner_name_hint'),
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'owner_name', 'id' => 'owner_name',
'name' => 'owner_name', 'name' => 'owner_name',
@ -202,6 +209,7 @@
[], [],
lang('Podcast.form.owner_email_hint'), lang('Podcast.form.owner_email_hint'),
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'owner_email', 'id' => 'owner_email',
'name' => 'owner_email', 'name' => 'owner_email',
@ -218,6 +226,7 @@
lang('Podcast.form.publisher_hint'), lang('Podcast.form.publisher_hint'),
true, true,
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'publisher', 'id' => 'publisher',
'name' => 'publisher', 'name' => 'publisher',
@ -226,6 +235,7 @@
]) ?> ]) ?>
<?= form_label(lang('Podcast.form.copyright'), 'copyright', [], '', true) ?> <?= form_label(lang('Podcast.form.copyright'), 'copyright', [], '', true) ?>
<?= form_input([ <?= form_input([
'id' => 'copyright', 'id' => 'copyright',
'name' => 'copyright', 'name' => 'copyright',
@ -247,12 +257,14 @@
lang('Podcast.form.location_name_hint'), lang('Podcast.form.location_name_hint'),
true, true,
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'location_name', 'id' => 'location_name',
'name' => 'location_name', 'name' => 'location_name',
'class' => 'form-input mb-4', 'class' => 'form-input mb-4',
'value' => old('location_name', $podcast->location_name), 'value' => old('location_name', $podcast->location_name),
]) ?> ]) ?>
<?= form_section_close() ?> <?= form_section_close() ?>
<?= form_section( <?= form_section(
@ -267,6 +279,7 @@
lang('Podcast.form.payment_pointer_hint'), lang('Podcast.form.payment_pointer_hint'),
true, true,
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'payment_pointer', 'id' => 'payment_pointer',
'name' => 'payment_pointer', 'name' => 'payment_pointer',
@ -285,41 +298,41 @@
true, true,
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'partner_id', 'id' => 'partner_id',
'name' => 'partner_id', 'name' => 'partner_id',
'class' => 'form-input w-full', 'class' => 'form-input w-full',
'value' => old('partner_id', $podcast->partner_id), 'value' => old('partner_id', $podcast->partner_id),
]) ?> ]) ?>
</div> </div>
<div class="flex flex-col flex-1"> <div class="flex flex-col flex-1">
<?= form_label( <?= form_label(
lang('Podcast.form.partner_link_url'), lang('Podcast.form.partner_link_url'),
'partner_link_url', 'partner_link_url',
[], [],
lang('Podcast.form.partner_link_url_hint'), lang('Podcast.form.partner_link_url_hint'),
true, true,
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'partner_link_url', 'id' => 'partner_link_url',
'name' => 'partner_link_url', 'name' => 'partner_link_url',
'class' => 'form-input w-full', 'class' => 'form-input w-full',
'value' => old('partner_link_url', $podcast->partner_link_url), 'value' => old('partner_link_url', $podcast->partner_link_url),
]) ?> ]) ?>
</div> </div>
<div class="flex flex-col flex-1"> <div class="flex flex-col flex-1">
<?= form_label( <?= form_label(
lang('Podcast.form.partner_image_url'), lang('Podcast.form.partner_image_url'),
'partner_image_url', 'partner_image_url',
[], [],
lang('Podcast.form.partner_image_url_hint'), lang('Podcast.form.partner_image_url_hint'),
true, true,
) ?> ) ?>
<?= form_input([ <?= form_input([
'id' => 'partner_image_url', 'id' => 'partner_image_url',
'name' => 'partner_image_url', 'name' => 'partner_image_url',
'class' => 'form-input w-full', 'class' => 'form-input w-full',
'value' => old('partner_image_url', $podcast->partner_image_url), 'value' => old('partner_image_url', $podcast->partner_image_url),
]) ?> ]) ?>
</div> </div>
</div> </div>
<?= form_section_close() ?> <?= form_section_close() ?>
@ -328,6 +341,7 @@
lang('Podcast.form.advanced_section_title'), lang('Podcast.form.advanced_section_title'),
lang('Podcast.form.advanced_section_subtitle'), lang('Podcast.form.advanced_section_subtitle'),
) ?> ) ?>
<?= form_label( <?= form_label(
lang('Podcast.form.custom_rss'), lang('Podcast.form.custom_rss'),
'custom_rss', 'custom_rss',
@ -335,12 +349,17 @@
lang('Podcast.form.custom_rss_hint'), lang('Podcast.form.custom_rss_hint'),
true, true,
) ?> ) ?>
<?= xml_editor([
'id' => 'custom_rss', <?= component('Forms/XMLEditor',
'name' => 'custom_rss', [
'class' => 'form-textarea', 'content' => old('custom_rss', $podcast->custom_rss_string)
'value' => old('custom_rss', $podcast->custom_rss_string), ],
]) ?> [
'id' => 'custom_rss',
'name' => 'custom_rss',
]
) ?>
<?= form_section_close() ?> <?= form_section_close() ?>
<?= form_section( <?= form_section(
@ -348,40 +367,62 @@
lang('Podcast.form.status_section_subtitle'), lang('Podcast.form.status_section_subtitle'),
) ?> ) ?>
<?= form_switch( <?= component(
lang('Podcast.form.block'), 'Forms/Toggler',
['id' => 'block', 'name' => 'block'], [
'yes', 'label' => lang('Podcast.form.lock'),
old('block', $podcast->is_blocked), 'hint' => lang('Podcast.form.lock_hint'),
'mb-2', ],
[
'id' => 'lock',
'name' => 'lock',
'value' => 'yes',
'checked' => old('complete', $podcast->is_locked),
'class' => 'mb-2'
]
) ?> ) ?>
<?= form_switch( <?= component(
lang('Podcast.form.complete'), 'Forms/Toggler',
['id' => 'complete', 'name' => 'complete'], [
'yes', 'label' => lang('Podcast.form.block'),
old('complete', $podcast->is_completed), ],
'mb-2', [
'id' => 'block',
'name' => 'block',
'value' => 'yes',
'checked' => old('block', $podcast->is_blocked),
'class' => 'mb-2'
]
) ?> ) ?>
<?= form_switch( <?= component(
lang('Podcast.form.lock') . 'Forms/Toggler',
hint_tooltip(lang('Podcast.form.lock_hint'), 'ml-1'), [
['id' => 'lock', 'name' => 'lock'], 'label' => lang('Podcast.form.complete'),
'yes', ],
old('lock', $podcast->is_locked), [
'id' => 'complete',
'name' => 'complete',
'value' => 'yes',
'checked' => old('complete', $podcast->is_completed),
]
) ?> ) ?>
<?= form_section_close() ?> <?= form_section_close() ?>
<?= button( <?= component(
lang('Podcast.form.submit_edit'), 'Button',
'', [
['variant' => 'primary'], 'label' => lang('Podcast.form.submit_edit'),
['type' => 'submit', 'class' => 'self-end'], 'variant' => 'primary',
],
[
'type' => 'submit',
'class' => 'self-end'
]
) ?> ) ?>
<?= form_close() ?> <?= form_close() ?>
<?= $this->endSection() ?> <?= $this->endSection() ?>

View File

@ -99,10 +99,12 @@
[], [],
lang('Person.podcast_form.persons_hint'), lang('Person.podcast_form.persons_hint'),
) ?> ) ?>
<?= form_multiselect('persons[]', $personOptions, old('persons', []), [ <?= component('Forms/MultiSelect', ['options' => $personOptions, 'selected' => old('persons', [])], [
'id' => 'persons', 'id' => 'persons',
'class' => 'form-select mb-4', 'name' => 'persons[]',
'class' => 'mb-4',
'required' => 'required', 'required' => 'required',
'data-max-item-count' => '2',
]) ?> ]) ?>
<?= form_label( <?= form_label(
@ -112,9 +114,10 @@
lang('Person.podcast_form.roles_hint'), lang('Person.podcast_form.roles_hint'),
true, true,
) ?> ) ?>
<?= form_multiselect('roles[]', $taxonomyOptions, old('roles', []), [ <?= component('Forms/MultiSelect', ['options' => $taxonomyOptions, 'selected' => old('roles', [])], [
'id' => 'roles', 'id' => 'roles',
'class' => 'form-select mb-4', 'name' => 'roles[]',
'class' => 'mb-4',
]) ?> ]) ?>
<?= form_section_close() ?> <?= form_section_close() ?>

View File

@ -103,34 +103,39 @@
'type' => 'text', 'type' => 'text',
'placeholder' => lang("Platforms.description.{$platform->type}"), 'placeholder' => lang("Platforms.description.{$platform->type}"),
]) ?> ]) ?>
<?= form_switch( <?= component(
lang('Platforms.visible'), 'Forms/Toggler',
[
'label' => lang('Platforms.visible'),
],
[ [
'id' => $platform->slug . '_visible', 'id' => $platform->slug . '_visible',
'name' => 'platforms[' . $platform->slug . '][visible]', 'name' => 'platforms[' . $platform->slug . '][visible]',
], 'value' => 'yes',
'yes', 'checked' => old(
old( $platform->slug . '_visible',
$platform->slug . '_visible', $platform->is_visible ? $platform->is_visible : false,
$platform->is_visible ? $platform->is_visible : false, ),
), 'class' => 'text-sm mb-1'
'text-sm mb-1', ]
) ?> ) ?>
<?= form_switch( <?= component(
lang('Platforms.on_embeddable_player'), 'Forms/Toggler',
[
'label' => lang('Platforms.on_embeddable_player'),
],
[ [
'id' => $platform->slug . '_on_embeddable_player', 'id' => $platform->slug . '_on_embeddable_player',
'name' => 'name' => 'platforms[' . $platform->slug . '][on_embeddable_player]',
'platforms[' . $platform->slug . '][on_embeddable_player]', 'value' => 'yes',
], 'checked' => old(
'yes', $platform->slug . '_on_embeddable_player',
old( $platform->is_on_embeddable_player
$platform->slug . '_on_embeddable_player', ? $platform->is_on_embeddable_player
$platform->is_on_embeddable_player : false,
? $platform->is_on_embeddable_player ),
: false, 'class' => 'text-sm'
), ]
'text-sm',
) ?> ) ?>
</div> </div>
</div> </div>

View File

@ -17,8 +17,9 @@
<?= csrf_field() ?> <?= csrf_field() ?>
<?= form_label(lang('User.form.roles'), 'roles') ?> <?= form_label(lang('User.form.roles'), 'roles') ?>
<?= form_multiselect('roles[]', $roleOptions, $user->roles, [ <?= component('Forms/MultiSelect', ['options' => $roleOptions, 'selected' => $user->roles], [
'id' => 'roles', 'id' => 'roles',
'name' => 'roles[]',
'class' => 'mb-4', 'class' => 'mb-4',
]) ?> ]) ?>

View File

View File

@ -1,4 +1,4 @@
<?= $this->include('podcast/_partials/comment_card_authenticated') ?> <?= $this->include('podcast/_partials/comment_card_authenticated') ?>
<div class="-mt-2 overflow-hidden border-b border-l border-r post-replies rounded-b-xl"> <div class="-mt-2 overflow-hidden border-b border-l border-r post-replies rounded-b-xl">
<?= form_open( <?= form_open(
route_to('comment-attempt-reply', $podcast->id, $episode->id, $comment->id), route_to('comment-attempt-reply', $podcast->id, $episode->id, $comment->id),

View File

@ -38,7 +38,6 @@
'Podcast.episodes', 'Podcast.episodes',
) ?></a> ) ?></a>
</nav> </nav>
<section class="max-w-2xl px-6 py-8 mx-auto space-y-8"> <section class="max-w-2xl px-6 py-8 mx-auto space-y-8">
<?php foreach ($posts as $post): ?> <?php foreach ($posts as $post): ?>
<?php if ($post->reblog_of_id !== null): ?> <?php if ($post->reblog_of_id !== null): ?>

View File

@ -1,6 +1,7 @@
<?php <?php
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symplify\CodingStandard\Fixer\Naming\StandardizeHereNowDocKeywordFixer;
use Symplify\EasyCodingStandard\ValueObject\Option; use Symplify\EasyCodingStandard\ValueObject\Option;
use Symplify\EasyCodingStandard\ValueObject\Set\SetList; use Symplify\EasyCodingStandard\ValueObject\Set\SetList;
@ -20,6 +21,10 @@ return static function (ContainerConfigurator $containerConfigurator): void {
// skip specific generated files // skip specific generated files
__DIR__ . '/app/Language/*/PersonsTaxonomy.php', __DIR__ . '/app/Language/*/PersonsTaxonomy.php',
StandardizeHereNowDocKeywordFixer::class => [
__DIR__ . '/app/Views/Components',
]
]); ]);
$containerConfigurator->import(SetList::PSR_12); $containerConfigurator->import(SetList::PSR_12);

View File

@ -35,3 +35,4 @@ parameters:
- app/Helpers - app/Helpers
- app/Libraries/ActivityPub/Helpers - app/Libraries/ActivityPub/Helpers
- app/Libraries/Analytics/Helpers - app/Libraries/Analytics/Helpers
- app/Libraries/ViewComponents/Helpers

View File

@ -65,7 +65,7 @@ return static function (ContainerConfigurator $containerConfigurator): void {
], ],
]); ]);
// Path to phpstan with extensions, that PHPSTan in Rector uses to determine types // Path to phpstan with extensions, that PHPStan in Rector uses to determine types
$parameters->set( $parameters->set(
Option::PHPSTAN_FOR_RECTOR_PATH, Option::PHPSTAN_FOR_RECTOR_PATH,
__DIR__ . '/phpstan.neon', __DIR__ . '/phpstan.neon',

View File

@ -4,6 +4,7 @@ module.exports = {
mode: "jit", mode: "jit",
purge: [ purge: [
"./app/Views/**/*.php", "./app/Views/**/*.php",
"./app/View/Components/**/*.php",
"./app/Helpers/*.php", "./app/Helpers/*.php",
"./app/Resources/**/*.ts", "./app/Resources/**/*.ts",
], ],