mirror of
https://code.castopod.org/adaures/castopod
synced 2025-04-19 04:51:17 +00:00
feat(plugins): add group field type + multiple option to render field arrays
- update docs - render hint and helper options for all fields - replace option's hint with description
This commit is contained in:
parent
f50098ec89
commit
11ccd0ebe7
@ -23,6 +23,7 @@ import "./modules/video-clip-previewer";
|
||||
import VideoClipBuilder from "./modules/VideoClipBuilder";
|
||||
import "./modules/xml-editor";
|
||||
import "@patternfly/elements/pf-tabs/pf-tabs.js";
|
||||
import FieldArray from "./modules/FieldArray";
|
||||
|
||||
Dropdown();
|
||||
Tooltip();
|
||||
@ -39,3 +40,4 @@ PublishMessageWarning();
|
||||
HotKeys();
|
||||
ValidateFileSize();
|
||||
VideoClipBuilder();
|
||||
FieldArray();
|
||||
|
159
app/Resources/js/modules/FieldArray.ts
Normal file
159
app/Resources/js/modules/FieldArray.ts
Normal file
@ -0,0 +1,159 @@
|
||||
import Tooltip from "./Tooltip";
|
||||
|
||||
const FieldArray = (): void => {
|
||||
const fieldArrays: NodeListOf<HTMLElement> =
|
||||
document.querySelectorAll("[data-field-array]");
|
||||
|
||||
for (let i = 0; i < fieldArrays.length; i++) {
|
||||
const fieldArray = fieldArrays[i];
|
||||
const fieldArrayContainer = fieldArray.querySelector(
|
||||
"[data-field-array-container]"
|
||||
);
|
||||
const items: NodeListOf<HTMLElement> = fieldArray.querySelectorAll(
|
||||
"[data-field-array-item]"
|
||||
);
|
||||
const addButton = fieldArray.querySelector(
|
||||
"button[data-field-array-add]"
|
||||
) as HTMLButtonElement;
|
||||
|
||||
const deleteButtons: NodeListOf<HTMLButtonElement> =
|
||||
fieldArray.querySelectorAll("[data-field-array-delete]");
|
||||
|
||||
deleteButtons.forEach((deleteBtn) => {
|
||||
deleteBtn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
deleteBtn.blur();
|
||||
fieldArrayContainer
|
||||
?.querySelector(
|
||||
`[data-field-array-item="${deleteBtn.dataset.fieldArrayDelete}"]`
|
||||
)
|
||||
?.remove();
|
||||
});
|
||||
});
|
||||
|
||||
// create base element to clone
|
||||
const baseItem = items[0].cloneNode(true) as HTMLElement;
|
||||
|
||||
const elements: NodeListOf<HTMLFormElement> = baseItem.querySelectorAll(
|
||||
"input, select, textarea"
|
||||
);
|
||||
|
||||
elements.forEach((element) => {
|
||||
element.value = "";
|
||||
});
|
||||
|
||||
if (fieldArrayContainer && addButton) {
|
||||
addButton.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const newItem = baseItem.cloneNode(true) as HTMLElement;
|
||||
|
||||
const deleteBtn: HTMLButtonElement | null = newItem.querySelector(
|
||||
"button[data-field-array-delete]"
|
||||
);
|
||||
|
||||
if (deleteBtn) {
|
||||
deleteBtn.addEventListener("click", () => {
|
||||
deleteBtn.blur();
|
||||
newItem.remove();
|
||||
});
|
||||
|
||||
fieldArrayContainer.appendChild(newItem);
|
||||
newItem.scrollIntoView({
|
||||
behavior: "auto",
|
||||
block: "center",
|
||||
inline: "center",
|
||||
});
|
||||
|
||||
// reload tooltip module for showing remove button label
|
||||
Tooltip();
|
||||
|
||||
// focus to first form element if mouse click
|
||||
if (event.screenX !== 0 && event.screenY !== 0) {
|
||||
const elements: NodeListOf<HTMLFormElement> =
|
||||
newItem.querySelectorAll("input, select, textarea");
|
||||
|
||||
if (elements.length > 0) {
|
||||
elements[0].focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const updateIndexes = () => {
|
||||
// get last child item to set item count
|
||||
const items: NodeListOf<HTMLElement> =
|
||||
fieldArrayContainer.querySelectorAll("[data-field-array-item]");
|
||||
|
||||
let itemIndex = 0;
|
||||
items.forEach((item) => {
|
||||
const itemNumber: HTMLElement | null = item.querySelector(
|
||||
"[data-field-array-number]"
|
||||
);
|
||||
|
||||
if (itemNumber) {
|
||||
itemNumber.innerHTML = "#";
|
||||
const indexNum = itemIndex + 1;
|
||||
if (item.dataset.fieldArrayItem !== itemIndex.toString()) {
|
||||
item.classList.add("motion-safe:animate-single-pulse");
|
||||
setTimeout(() => {
|
||||
item.classList.remove("motion-safe:animate-single-pulse");
|
||||
itemNumber.innerHTML = indexNum.toString();
|
||||
}, 300);
|
||||
} else {
|
||||
itemNumber.innerHTML = indexNum.toString();
|
||||
}
|
||||
}
|
||||
|
||||
item.dataset.fieldArrayItem = itemIndex.toString();
|
||||
const deleteBtn = item.querySelector(
|
||||
"button[data-field-array-delete]"
|
||||
) as HTMLButtonElement | null;
|
||||
|
||||
if (deleteBtn) {
|
||||
deleteBtn.dataset.fieldArrayDelete = itemIndex.toString();
|
||||
}
|
||||
|
||||
const itemElements: NodeListOf<HTMLFormElement> =
|
||||
item.querySelectorAll("input, select, textarea");
|
||||
|
||||
itemElements.forEach((element) => {
|
||||
const label: HTMLLabelElement | null = item.querySelector(
|
||||
`label[for="${element.id}"]`
|
||||
);
|
||||
|
||||
const elementID = element.name.replace(
|
||||
/(.*\[)\d+?(\].*)/g,
|
||||
`$1${itemIndex}$2`
|
||||
);
|
||||
|
||||
if (label) {
|
||||
label.htmlFor = elementID;
|
||||
}
|
||||
|
||||
element.id = elementID;
|
||||
element.name = elementID;
|
||||
});
|
||||
|
||||
itemIndex++;
|
||||
});
|
||||
};
|
||||
|
||||
// add mutation observer to run index updates when field array
|
||||
// items are added or removed
|
||||
const callback = function (mutationList: MutationRecord[]) {
|
||||
for (const mutation of mutationList) {
|
||||
if (mutation.type === "childList") {
|
||||
updateIndexes();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const observer = new MutationObserver(callback);
|
||||
|
||||
observer.observe(fieldArrayContainer, { childList: true });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default FieldArray;
|
@ -1,3 +1,13 @@
|
||||
@layer base {
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.form-helper {
|
||||
@apply text-skin-muted;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.post-content {
|
||||
& a {
|
||||
@ -78,4 +88,13 @@
|
||||
#facc15 20px
|
||||
);
|
||||
}
|
||||
|
||||
.divide-fieldset-y > :not([hidden], legend) ~ :not([hidden], legend) {
|
||||
@apply pt-4;
|
||||
|
||||
--tw-divide-y-reverse: 0;
|
||||
|
||||
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
||||
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,33 @@
|
||||
@layer components {
|
||||
.form-radio-btn {
|
||||
@apply absolute mt-3 ml-3 border-contrast border-3 text-accent-base;
|
||||
@apply absolute right-4 top-4 border-contrast border-3 text-accent-base;
|
||||
|
||||
&:focus {
|
||||
@apply ring-accent;
|
||||
}
|
||||
|
||||
&:checked {
|
||||
@apply ring-2 ring-contrast;
|
||||
|
||||
& + label {
|
||||
@apply text-accent-contrast bg-accent-base;
|
||||
@apply text-accent-hover bg-base border-accent-base shadow-none;
|
||||
}
|
||||
|
||||
& + label .form-radio-btn-description {
|
||||
@apply text-accent-base;
|
||||
}
|
||||
}
|
||||
|
||||
& + label {
|
||||
@apply inline-flex items-center py-2 pl-8 pr-2 text-sm font-semibold rounded-lg cursor-pointer border-contrast bg-elevated border-3;
|
||||
@apply h-full w-full inline-flex flex-col items-start py-3 px-4 text-sm font-bold rounded-lg cursor-pointer border-contrast bg-elevated border-3 transition-all;
|
||||
|
||||
color: hsl(var(--color-text-muted));
|
||||
box-shadow: 2px 2px 0 hsl(var(--color-border-contrast));
|
||||
}
|
||||
|
||||
& + label span {
|
||||
@apply pr-8;
|
||||
}
|
||||
|
||||
& + label .form-radio-btn-description {
|
||||
@apply font-normal text-xs text-skin-muted text-balance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ class Checkbox extends FormComponent
|
||||
|
||||
protected string $hint = '';
|
||||
|
||||
protected string $helper = '';
|
||||
|
||||
protected bool $isChecked = false;
|
||||
|
||||
#[Override]
|
||||
@ -37,10 +39,26 @@ class Checkbox extends FormComponent
|
||||
'slot' => $this->hint,
|
||||
]))->render();
|
||||
|
||||
$this->mergeClass('inline-flex items-center');
|
||||
$this->mergeClass('inline-flex items-start gap-x-2');
|
||||
|
||||
$helperText = '';
|
||||
if ($this->helper !== '') {
|
||||
$helperId = $this->name . 'Help';
|
||||
$helperText = (new Helper([
|
||||
'id' => $helperId,
|
||||
'slot' => $this->helper,
|
||||
'class' => '-mt-1',
|
||||
]))->render();
|
||||
$this->attributes['aria-describedby'] = $helperId;
|
||||
}
|
||||
|
||||
return <<<HTML
|
||||
<label {$this->getStringifiedAttributes()}>{$checkboxInput}<span class="ml-2">{$this->slot}{$hint}</span></label>
|
||||
<label {$this->getStringifiedAttributes()}>{$checkboxInput}
|
||||
<div class="flex flex-col">
|
||||
<span>{$this->slot}{$hint}</span>
|
||||
{$helperText}
|
||||
</div>
|
||||
</label>
|
||||
HTML;
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ class Helper extends Component
|
||||
#[Override]
|
||||
public function render(): string
|
||||
{
|
||||
$this->mergeClass('text-skin-muted');
|
||||
$this->mergeClass('form-helper');
|
||||
|
||||
return <<<HTML
|
||||
<small {$this->getStringifiedAttributes()}>{$this->slot}</small>
|
||||
|
@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Views\Components\Forms;
|
||||
|
||||
use App\Views\Components\Hint;
|
||||
use Override;
|
||||
|
||||
class RadioButton extends FormComponent
|
||||
@ -17,7 +16,7 @@ class RadioButton extends FormComponent
|
||||
|
||||
protected bool $isSelected = false;
|
||||
|
||||
protected string $hint = '';
|
||||
protected string $description = '';
|
||||
|
||||
#[Override]
|
||||
public function render(): string
|
||||
@ -32,21 +31,30 @@ class RadioButton extends FormComponent
|
||||
$data['required'] = 'required';
|
||||
}
|
||||
|
||||
$this->mergeClass('relative w-full');
|
||||
|
||||
$descriptionText = '';
|
||||
if ($this->description !== '') {
|
||||
$describerId = $this->name . 'Help';
|
||||
$descriptionText = <<<HTML
|
||||
<span id="{$describerId}" class="form-radio-btn-description">{$this->description}</span>
|
||||
HTML;
|
||||
$data['aria-describedby'] = $describerId;
|
||||
}
|
||||
|
||||
$radioInput = form_radio(
|
||||
$data,
|
||||
$this->value,
|
||||
old($this->name) ? old($this->name) === $this->value : $this->isSelected,
|
||||
);
|
||||
|
||||
$hint = $this->hint === '' ? '' : (new Hint([
|
||||
'class' => 'ml-1 text-base',
|
||||
'slot' => $this->hint,
|
||||
]))->render();
|
||||
|
||||
return <<<HTML
|
||||
<div {$this->getStringifiedAttributes()}">
|
||||
{$radioInput}
|
||||
<label for="{$this->value}">{$this->slot}{$hint}</label>
|
||||
<label for="{$this->value}">
|
||||
<span>{$this->slot}</span>
|
||||
{$descriptionText}
|
||||
</label>
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
|
@ -22,10 +22,10 @@ class RadioGroup extends FormComponent
|
||||
*/
|
||||
protected array $options = [];
|
||||
|
||||
protected string $helper = '';
|
||||
|
||||
protected string $hint = '';
|
||||
|
||||
protected string $helper = '';
|
||||
|
||||
#[Override]
|
||||
public function render(): string
|
||||
{
|
||||
@ -34,12 +34,12 @@ class RadioGroup extends FormComponent
|
||||
$options = '';
|
||||
foreach ($this->options as $option) {
|
||||
$options .= (new RadioButton([
|
||||
'value' => $option['value'],
|
||||
'name' => $this->name,
|
||||
'slot' => $option['label'],
|
||||
'hint' => $option['hint'] ?? '',
|
||||
'isSelected' => var_export($this->value === null ? ($option['value'] === $this->options[array_key_first($this->options)]['value']) : ($this->value === $option['value']), true),
|
||||
'isRequired' => var_export($this->isRequired, true),
|
||||
'value' => $option['value'],
|
||||
'name' => $this->name,
|
||||
'slot' => $option['label'],
|
||||
'description' => $option['description'] ?? '',
|
||||
'isSelected' => var_export($this->value === null ? ($option['value'] === $this->options[array_key_first($this->options)]['value']) : ($this->value === $option['value']), true),
|
||||
'isRequired' => var_export($this->isRequired, true),
|
||||
]))->render();
|
||||
}
|
||||
|
||||
@ -62,7 +62,7 @@ class RadioGroup extends FormComponent
|
||||
<fieldset {$this->getStringifiedAttributes()}>
|
||||
<legend class="-mb-1 text-sm font-semibold">{$this->label}{$hint}</legend>
|
||||
{$helperText}
|
||||
<div class="flex gap-1 mt-1">{$options}</div>
|
||||
<div class="grid grid-cols-radioGroup gap-2 mt-1">{$options}</div>
|
||||
</fieldset>
|
||||
HTML;
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ class Select extends FormComponent
|
||||
$options = '';
|
||||
$selected = $this->value ?? $this->defaultValue;
|
||||
foreach ($this->options as $option) {
|
||||
$options .= '<option ' . (array_key_exists('hint', $option) ? 'data-label-description="' . $option['hint'] . '" ' : '') . 'value="' . $option['value'] . '"' . ($option['value'] === $selected ? ' selected' : '') . '>' . $option['label'] . '</option>';
|
||||
$options .= '<option ' . (array_key_exists('description', $option) ? 'data-label-description="' . $option['description'] . '" ' : '') . 'value="' . $option['value'] . '"' . ($option['value'] === $selected ? ' selected' : '') . '>' . $option['label'] . '</option>';
|
||||
}
|
||||
|
||||
$this->attributes['name'] = $this->name;
|
||||
|
@ -45,7 +45,7 @@ class SelectMulti extends FormComponent
|
||||
$options = '';
|
||||
$selected = $this->value ?? $this->defaultValue;
|
||||
foreach ($this->options as $option) {
|
||||
$options .= '<option ' . (array_key_exists('hint', $option) ? 'data-label-description="' . $option['hint'] . '" ' : '') . 'value="' . $option['value'] . '"' . (in_array($option['value'], $selected, true) ? ' selected' : '') . '>' . $option['label'] . '</option>';
|
||||
$options .= '<option ' . (array_key_exists('description', $option) ? 'data-label-description="' . $option['description'] . '" ' : '') . 'value="' . $option['value'] . '"' . (in_array($option['value'], $selected, true) ? ' selected' : '') . '>' . $option['label'] . '</option>';
|
||||
}
|
||||
|
||||
$this->attributes['name'] = $this->name . '[]';
|
||||
|
@ -17,12 +17,14 @@ class Toggler extends FormComponent
|
||||
|
||||
protected string $hint = '';
|
||||
|
||||
protected string $helper = '';
|
||||
|
||||
protected bool $isChecked = false;
|
||||
|
||||
#[Override]
|
||||
public function render(): string
|
||||
{
|
||||
$this->mergeClass('relative justify-between inline-flex items-center gap-x-2');
|
||||
$this->mergeClass('relative justify-between inline-flex items-start gap-x-2');
|
||||
|
||||
$checkbox = form_checkbox(
|
||||
[
|
||||
@ -39,9 +41,23 @@ class Toggler extends FormComponent
|
||||
'slot' => $this->hint,
|
||||
]))->render();
|
||||
|
||||
$helperText = '';
|
||||
if ($this->helper !== '') {
|
||||
$helperId = $this->name . 'Help';
|
||||
$helperText = (new Helper([
|
||||
'id' => $helperId,
|
||||
'slot' => $this->helper,
|
||||
'class' => '-mt-1',
|
||||
]))->render();
|
||||
$this->attributes['aria-describedby'] = $helperId;
|
||||
}
|
||||
|
||||
return <<<HTML
|
||||
<label {$this->getStringifiedAttributes()}>
|
||||
<span>{$this->slot}{$hint}</span>
|
||||
<div class="flex flex-col">
|
||||
<span>{$this->slot}{$hint}</span>
|
||||
{$helperText}
|
||||
</div>
|
||||
{$checkbox}
|
||||
<span class="form-switch-slider"></span>
|
||||
</label>
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?= helper(['components', 'svg']) ?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="<?= service('request')
|
||||
->getLocale() ?>">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?= helper(['components', 'svg']) ?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="<?= service('request')
|
||||
->getLocale() ?>">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
@ -101,14 +101,16 @@ each property being a field key and the value being a `Field` object.
|
||||
|
||||
A field is a form element:
|
||||
|
||||
| Property | Type | Note |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
|
||||
| `type` | `checkbox` \| `datetime` \| `email` \| `markdown` \| `number` \| `radio-group` \| `select-multiple` \| `select` \| `text` \| `textarea` \| `toggler` \| `url` | Default is `text` |
|
||||
| `label` (required) | `string` | Can be translated (see i18n) |
|
||||
| `hint` | `string` | Can be translated (see i18n) |
|
||||
| `helper` | `string` | Can be translated (see i18n) |
|
||||
| `optional` | `boolean` | Default is `false` |
|
||||
| `options` | `Options` | Required for `radio-group`, `select-multiple`, and `select` types. |
|
||||
| Property | Type | Note |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------ |
|
||||
| `type` | `checkbox` \| `datetime` \| `email` \| `group` \| `markdown` \| `number` \| `radio-group` \| `select-multiple` \| `select` \| `text` \| `textarea` \| `toggler` \| `url` | Default is `text` |
|
||||
| `label` (required) | `string` | Can be translated (see i18n) |
|
||||
| `hint` | `string` | Can be translated (see i18n) |
|
||||
| `helper` | `string` | Can be translated (see i18n) |
|
||||
| `optional` | `boolean` | Default is `false` |
|
||||
| `options` | `Options` | Required for `radio-group`, `select-multiple`, and `select` types. |
|
||||
| `multiple` | `boolean` | Default is `false` |
|
||||
| `fields` | `Array<string, Field>` | Required for `group` type |
|
||||
|
||||
#### Options object
|
||||
|
||||
@ -119,7 +121,7 @@ The `Options` object properties are option keys and the value is an `Option`.
|
||||
| Property | Type | Note |
|
||||
| ------------------ | -------- | ---------------------------- |
|
||||
| `label` (required) | `string` | Can be translated (see i18n) |
|
||||
| `hint` | `string` | Can be translated (see i18n) |
|
||||
| `description` | `string` | Can be translated (see i18n) |
|
||||
|
||||
### files
|
||||
|
||||
|
@ -38,6 +38,10 @@ return [
|
||||
'noChoicesText' => 'No choices to choose from',
|
||||
'maxItemText' => 'Cannot add more items',
|
||||
],
|
||||
'fieldArray' => [
|
||||
'add' => 'Add',
|
||||
'remove' => 'Remove',
|
||||
],
|
||||
'upload_file' => 'Upload a file',
|
||||
'remote_url' => 'Remote URL',
|
||||
'save' => 'Save',
|
||||
|
@ -109,11 +109,11 @@ return [
|
||||
'type' => [
|
||||
'label' => 'Type',
|
||||
'full' => 'Full',
|
||||
'full_hint' => 'Complete content (the episode)',
|
||||
'full_description' => 'Complete content (the episode)',
|
||||
'trailer' => 'Trailer',
|
||||
'trailer_hint' => 'Short, promotional piece of content that represents a preview of the current show',
|
||||
'trailer_description' => 'Short, promotional piece of content that represents a preview of the current show',
|
||||
'bonus' => 'Bonus',
|
||||
'bonus_hint' => 'Extra content for the show (for example, behind the scenes info or interviews with the cast) or cross-promotional content for another show',
|
||||
'bonus_description' => 'Extra content for the show (for example, behind the scenes info or interviews with the cast) or cross-promotional content for another show',
|
||||
],
|
||||
'premium_title' => 'Premium',
|
||||
'premium' => 'Episode must be accessible to premium subscribers only',
|
||||
|
@ -72,19 +72,19 @@ return [
|
||||
'type' => [
|
||||
'label' => 'Type',
|
||||
'episodic' => 'Episodic',
|
||||
'episodic_hint' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.',
|
||||
'episodic_description' => 'If episodes are intended to be consumed without any specific order. Newest episodes will be presented first.',
|
||||
'serial' => 'Serial',
|
||||
'serial_hint' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.',
|
||||
'serial_description' => 'If episodes are intended to be consumed in sequential order. Episodes will be presented in numeric order.',
|
||||
],
|
||||
'medium' => [
|
||||
'label' => 'Medium',
|
||||
'hint' => 'Medium as represented by podcast:medium tag in RSS. Changing this may change how players present your feed.',
|
||||
'podcast' => 'Podcast',
|
||||
'podcast_hint' => 'Describes a feed for a podcast show.',
|
||||
'podcast_description' => 'Describes a feed for a podcast show.',
|
||||
'music' => 'Music',
|
||||
'music_hint' => 'A feed of music organized into an "album" with each item a song within the album.',
|
||||
'music_description' => 'A feed of music organized into an "album" with each item a song within the album.',
|
||||
'audiobook' => 'Audiobook',
|
||||
'audiobook_hint' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.',
|
||||
'audiobook_description' => 'Specific types of audio with one item per feed, or where items represent chapters within the book.',
|
||||
],
|
||||
'description' => 'Description',
|
||||
'classification_section_title' => 'Classification',
|
||||
|
@ -13,37 +13,41 @@ use CodeIgniter\HTTP\RedirectResponse;
|
||||
use CodeIgniter\HTTP\URI;
|
||||
use CodeIgniter\I18n\Time;
|
||||
use Modules\Admin\Controllers\BaseController;
|
||||
use Modules\Plugins\Core\BasePlugin;
|
||||
use Modules\Plugins\Core\Markdown;
|
||||
use Modules\Plugins\Core\Plugins;
|
||||
use Modules\Plugins\Manifest\Field;
|
||||
|
||||
class PluginController extends BaseController
|
||||
{
|
||||
protected Plugins $plugins;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->plugins = service('plugins');
|
||||
}
|
||||
|
||||
public function installed(): string
|
||||
{
|
||||
/** @var Plugins $plugins */
|
||||
$plugins = service('plugins');
|
||||
|
||||
$pager = service('pager');
|
||||
|
||||
$page = (int) ($this->request->getGet('page') ?? 1);
|
||||
$perPage = 10;
|
||||
$total = $plugins->getInstalledCount();
|
||||
$total = $this->plugins->getInstalledCount();
|
||||
|
||||
$pager_links = $pager->makeLinks($page, $perPage, $total);
|
||||
|
||||
return view('plugins/installed', [
|
||||
'total' => $total,
|
||||
'plugins' => $plugins->getPlugins($page, $perPage),
|
||||
'plugins' => $this->plugins->getPlugins($page, $perPage),
|
||||
'pager_links' => $pager_links,
|
||||
]);
|
||||
}
|
||||
|
||||
public function vendor(string $vendor): string
|
||||
{
|
||||
/** @var Plugins $plugins */
|
||||
$plugins = service('plugins');
|
||||
|
||||
$vendorPlugins = $plugins->getVendorPlugins($vendor);
|
||||
$vendorPlugins = $this->plugins->getVendorPlugins($vendor);
|
||||
replace_breadcrumb_params([
|
||||
$vendor => $vendor,
|
||||
]);
|
||||
@ -56,12 +60,10 @@ class PluginController extends BaseController
|
||||
|
||||
public function view(string $vendor, string $package): string
|
||||
{
|
||||
/** @var Plugins $plugins */
|
||||
$plugins = service('plugins');
|
||||
|
||||
$plugin = $plugins->getPlugin($vendor, $package);
|
||||
$plugin = $this->plugins->getPlugin($vendor, $package);
|
||||
|
||||
if ($plugin === null) {
|
||||
if (! $plugin instanceof BasePlugin) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
@ -80,12 +82,10 @@ class PluginController extends BaseController
|
||||
string $podcastId = null,
|
||||
string $episodeId = null
|
||||
): string {
|
||||
/** @var Plugins $plugins */
|
||||
$plugins = service('plugins');
|
||||
|
||||
$plugin = $plugins->getPlugin($vendor, $package);
|
||||
$plugin = $this->plugins->getPlugin($vendor, $package);
|
||||
|
||||
if ($plugin === null) {
|
||||
if (! $plugin instanceof BasePlugin) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
@ -146,12 +146,10 @@ class PluginController extends BaseController
|
||||
string $podcastId = null,
|
||||
string $episodeId = null
|
||||
): RedirectResponse {
|
||||
/** @var Plugins $plugins */
|
||||
$plugins = service('plugins');
|
||||
|
||||
$plugin = $plugins->getPlugin($vendor, $package);
|
||||
$plugin = $this->plugins->getPlugin($vendor, $package);
|
||||
|
||||
if ($plugin === null) {
|
||||
if (! $plugin instanceof BasePlugin) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
@ -170,12 +168,36 @@ class PluginController extends BaseController
|
||||
// construct validation rules first
|
||||
$rules = [];
|
||||
foreach ($plugin->getSettingsFields($type) as $field) {
|
||||
$typeRules = $plugins::FIELDS_VALIDATIONS[$field->type];
|
||||
$typeRules = $this->plugins::FIELDS_VALIDATIONS[$field->type];
|
||||
if (! in_array('permit_empty', $typeRules, true)) {
|
||||
$typeRules[] = $field->optional ? 'permit_empty' : 'required';
|
||||
}
|
||||
|
||||
$rules[$field->key] = $typeRules;
|
||||
if ($field->multiple) {
|
||||
if ($field->type === 'group') {
|
||||
foreach ($field->fields as $subField) {
|
||||
$typeRules = $this->plugins::FIELDS_VALIDATIONS[$subField->type];
|
||||
if (! in_array('permit_empty', $typeRules, true)) {
|
||||
$typeRules[] = $subField->optional ? 'permit_empty' : 'required';
|
||||
}
|
||||
|
||||
$rules[sprintf('%s.*.%s', $field->key, $subField->key)] = $typeRules;
|
||||
}
|
||||
} else {
|
||||
$rules[$field->key . '.*'] = $typeRules;
|
||||
}
|
||||
} elseif ($field->type === 'group') {
|
||||
foreach ($field->fields as $subField) {
|
||||
$typeRules = $this->plugins::FIELDS_VALIDATIONS[$subField->type];
|
||||
if (! in_array('permit_empty', $typeRules, true)) {
|
||||
$typeRules[] = $subField->optional ? 'permit_empty' : 'required';
|
||||
}
|
||||
|
||||
$rules[sprintf('%s.%s', $field->key, $subField->key)] = $typeRules;
|
||||
}
|
||||
} else {
|
||||
$rules[$field->key] = $typeRules;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $this->validate($rules)) {
|
||||
@ -188,20 +210,9 @@ class PluginController extends BaseController
|
||||
$validatedData = $this->validator->getValidated();
|
||||
|
||||
foreach ($plugin->getSettingsFields($type) as $field) {
|
||||
$value = $validatedData[$field->key] ?? null;
|
||||
$fieldValue = $value === '' ? null : match ($plugins::FIELDS_CASTS[$field->type] ?? 'text') {
|
||||
'bool' => $value === 'yes',
|
||||
'int' => (int) $value,
|
||||
'uri' => new URI($value),
|
||||
'datetime' => Time::createFromFormat(
|
||||
'Y-m-d H:i',
|
||||
$value,
|
||||
$this->request->getPost('client_timezone')
|
||||
)->setTimezone(app_timezone()),
|
||||
'markdown' => new Markdown($value),
|
||||
default => $value,
|
||||
};
|
||||
$plugins->setOption($plugin, $field->key, $fieldValue, $context);
|
||||
$fieldValue = $validatedData[$field->key] ?? null;
|
||||
|
||||
$this->plugins->setOption($plugin, $field->key, $this->castFieldValue($field, $fieldValue), $context);
|
||||
}
|
||||
|
||||
return redirect()->back()
|
||||
@ -212,49 +223,114 @@ class PluginController extends BaseController
|
||||
|
||||
public function activate(string $vendor, string $package): RedirectResponse
|
||||
{
|
||||
/** @var Plugins $plugins */
|
||||
$plugins = service('plugins');
|
||||
|
||||
$plugin = $plugins->getPlugin($vendor, $package);
|
||||
$plugin = $this->plugins->getPlugin($vendor, $package);
|
||||
|
||||
if ($plugin === null) {
|
||||
if (! $plugin instanceof BasePlugin) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$plugins->activate($plugin);
|
||||
$this->plugins->activate($plugin);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function deactivate(string $vendor, string $package): RedirectResponse
|
||||
{
|
||||
/** @var Plugins $plugins */
|
||||
$plugins = service('plugins');
|
||||
|
||||
$plugin = $plugins->getPlugin($vendor, $package);
|
||||
$plugin = $this->plugins->getPlugin($vendor, $package);
|
||||
|
||||
if ($plugin === null) {
|
||||
if (! $plugin instanceof BasePlugin) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$plugins->deactivate($plugin);
|
||||
$this->plugins->deactivate($plugin);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function uninstall(string $vendor, string $package): RedirectResponse
|
||||
{
|
||||
/** @var Plugins $plugins */
|
||||
$plugins = service('plugins');
|
||||
|
||||
$plugin = $plugins->getPlugin($vendor, $package);
|
||||
$plugin = $this->plugins->getPlugin($vendor, $package);
|
||||
|
||||
if ($plugin === null) {
|
||||
if (! $plugin instanceof BasePlugin) {
|
||||
throw PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$plugins->uninstall($plugin);
|
||||
$this->plugins->uninstall($plugin);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
private function castFieldValue(Field $field, mixed $fieldValue): mixed
|
||||
{
|
||||
if ($fieldValue === '' || $fieldValue === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = null;
|
||||
if ($field->multiple) {
|
||||
$value = [];
|
||||
foreach ($fieldValue as $key => $val) {
|
||||
if ($val === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($field->type === 'group') {
|
||||
foreach ($val as $subKey => $subVal) {
|
||||
/** @var Field|false $subField */
|
||||
$subField = array_column($field->fields, null, 'key')[$subKey] ?? false;
|
||||
if (! $subField) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$v = $this->castValue($subVal, $subField->type);
|
||||
if ($v) {
|
||||
$value[$key][$subKey] = $v;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$value[$key] = $this->castValue($val, $field->type);
|
||||
}
|
||||
}
|
||||
} elseif ($field->type === 'group') {
|
||||
foreach ($fieldValue as $subKey => $subVal) {
|
||||
/** @var Field|false $subField */
|
||||
$subField = array_column($field->fields, null, 'key')[$subKey] ?? false;
|
||||
if (! $subField) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$v = $this->castValue($subVal, $subField->type);
|
||||
if ($v) {
|
||||
$value[$subKey] = $v;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$value = $this->castValue($fieldValue, $field->type);
|
||||
}
|
||||
|
||||
return $value === [] ? null : $value;
|
||||
}
|
||||
|
||||
private function castValue(mixed $value, string $type): mixed
|
||||
{
|
||||
if ($value === '' || $value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return match ($this->plugins::FIELDS_CASTS[$type] ?? 'text') {
|
||||
'bool' => $value === 'yes',
|
||||
'int' => (int) $value,
|
||||
'uri' => new URI($value),
|
||||
'datetime' => Time::createFromFormat(
|
||||
'Y-m-d H:i',
|
||||
$value,
|
||||
$this->request->getPost('client_timezone')
|
||||
)->setTimezone(app_timezone()),
|
||||
'markdown' => new Markdown($value),
|
||||
default => $value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ class Plugins
|
||||
'textarea' => ['string'],
|
||||
'toggler' => ['permit_empty'],
|
||||
'url' => ['valid_url_strict'],
|
||||
'group' => ['permit_empty', 'is_list'],
|
||||
];
|
||||
|
||||
public const FIELDS_CASTS = [
|
||||
|
@ -7,27 +7,33 @@ namespace Modules\Plugins\Manifest;
|
||||
use Override;
|
||||
|
||||
/**
|
||||
* @property 'checkbox'|'datetime'|'email'|'markdown'|'number'|'radio-group'|'select-multiple'|'select'|'text'|'textarea'|'toggler'|'url'|'group' $type
|
||||
* @property string $key
|
||||
* @property 'text'|'email'|'url'|'markdown'|'number'|'switch' $type
|
||||
* @property string $label
|
||||
* @property string $hint
|
||||
* @property string $helper
|
||||
* @property bool $optional
|
||||
* @property Option[] $options
|
||||
* @property bool $multiple
|
||||
* @property Field[] $fields
|
||||
*/
|
||||
class Field extends ManifestObject
|
||||
{
|
||||
protected const VALIDATION_RULES = [
|
||||
'type' => 'permit_empty|in_list[checkbox,datetime,email,markdown,number,radio-group,select-multiple,select,text,textarea,toggler,url]',
|
||||
'type' => 'permit_empty|in_list[checkbox,datetime,email,markdown,number,radio-group,select-multiple,select,text,textarea,toggler,url,group]',
|
||||
'key' => 'required|alpha_dash',
|
||||
'label' => 'required|string',
|
||||
'hint' => 'permit_empty|string',
|
||||
'helper' => 'permit_empty|string',
|
||||
'optional' => 'permit_empty|is_boolean',
|
||||
'options' => 'permit_empty|is_list',
|
||||
'multiple' => 'permit_empty|is_boolean',
|
||||
'fields' => 'permit_empty|is_list',
|
||||
];
|
||||
|
||||
protected const CASTS = [
|
||||
'options' => [Option::class],
|
||||
'fields' => [self::class],
|
||||
];
|
||||
|
||||
protected string $type = 'text';
|
||||
@ -42,6 +48,8 @@ class Field extends ManifestObject
|
||||
|
||||
protected bool $optional = false;
|
||||
|
||||
protected bool $multiple = false;
|
||||
|
||||
/**
|
||||
* @var Option[]
|
||||
*/
|
||||
@ -60,37 +68,49 @@ class Field extends ManifestObject
|
||||
$data['options'] = $newOptions;
|
||||
}
|
||||
|
||||
if (array_key_exists('fields', $data)) {
|
||||
$newFields = [];
|
||||
foreach ($data['fields'] as $key => $field) {
|
||||
$field['key'] = $key;
|
||||
$newFields[] = $field;
|
||||
}
|
||||
|
||||
$data['fields'] = $newFields;
|
||||
}
|
||||
|
||||
parent::loadData($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{label:string,value:string,hint:string}[]
|
||||
* @return array{label:string,value:string,description:string}[]
|
||||
*/
|
||||
public function getOptionsArray(string $i18nKey): array
|
||||
public function getOptionsArray(string $pluginKey): array
|
||||
{
|
||||
$i18nKey = sprintf('%s.settings.%s.%s.options', $pluginKey, $this->type, $this->key);
|
||||
|
||||
$optionsArray = [];
|
||||
foreach ($this->options as $option) {
|
||||
$optionsArray[] = [
|
||||
'value' => $option->value,
|
||||
'label' => esc($this->getTranslated($i18nKey . '.' . $option->value . '.label', $option->label)),
|
||||
'hint' => esc($this->getTranslated($i18nKey . '.' . $option->value . '.hint', (string) $option->hint)),
|
||||
'value' => $option->value,
|
||||
'label' => $option->getTranslated($i18nKey, 'label'),
|
||||
'description' => $option->getTranslated($i18nKey, 'description'),
|
||||
];
|
||||
}
|
||||
|
||||
return $optionsArray;
|
||||
}
|
||||
|
||||
public function getTranslated(string $i18nKey, string $default): string
|
||||
public function getTranslated(string $pluginKey, string $property): string
|
||||
{
|
||||
$key = 'Plugin.' . $i18nKey;
|
||||
$key = sprintf('Plugin.%s.settings.%s.%s.%s', $pluginKey, $this->type, $this->key, $property);
|
||||
|
||||
/** @var string $i18nField */
|
||||
$i18nField = lang($key);
|
||||
|
||||
if ($default === '' || $i18nField === $key) {
|
||||
return $default;
|
||||
if ($this->{$property} === '' || $i18nField === $key) {
|
||||
return esc($this->{$property});
|
||||
}
|
||||
|
||||
return $i18nField;
|
||||
return esc($i18nField);
|
||||
}
|
||||
}
|
||||
|
@ -7,19 +7,33 @@ namespace Modules\Plugins\Manifest;
|
||||
/**
|
||||
* @property string $label
|
||||
* @property string $value
|
||||
* @property ?string $hint
|
||||
* @property string $hint
|
||||
*/
|
||||
class Option extends ManifestObject
|
||||
{
|
||||
protected const VALIDATION_RULES = [
|
||||
'label' => 'required|string',
|
||||
'value' => 'required|alpha_numeric_punct',
|
||||
'hint' => 'permit_empty|string',
|
||||
'label' => 'required|string',
|
||||
'value' => 'required|alpha_numeric_punct',
|
||||
'description' => 'permit_empty|string',
|
||||
];
|
||||
|
||||
protected string $label;
|
||||
|
||||
protected string $value;
|
||||
|
||||
protected ?string $hint = null;
|
||||
protected string $description = '';
|
||||
|
||||
public function getTranslated(string $i18nKey, string $property): string
|
||||
{
|
||||
$key = sprintf('%s.%s.%s', $i18nKey, $this->value, $property);
|
||||
|
||||
/** @var string $i18nField */
|
||||
$i18nField = lang($key);
|
||||
|
||||
if ($this->{$property} === '' || $i18nField === $key) {
|
||||
return esc($this->{$property});
|
||||
}
|
||||
|
||||
return esc($i18nField);
|
||||
}
|
||||
}
|
||||
|
@ -173,6 +173,7 @@
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"group",
|
||||
"checkbox",
|
||||
"datetime",
|
||||
"email",
|
||||
@ -206,12 +207,23 @@
|
||||
"^[A-Za-z0-9]+[\\w\\-\\:\\.]*$": { "$ref": "#/$defs/option" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"multiple": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"fields": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[A-Za-z]+[\\w\\-\\:\\.]*$": { "$ref": "#/$defs/field" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["label"],
|
||||
"additionalProperties": false,
|
||||
"allOf": [
|
||||
{ "$ref": "#/$defs/field-multiple-implies-options-is-required" }
|
||||
{ "$ref": "#/$defs/field-multiple-implies-options-is-required" },
|
||||
{ "$ref": "#/$defs/field-group-type-implies-fields-is-required" }
|
||||
]
|
||||
},
|
||||
"option": {
|
||||
@ -220,7 +232,7 @@
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"hint": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@ -245,6 +257,21 @@
|
||||
},
|
||||
{ "required": ["options"] }
|
||||
]
|
||||
},
|
||||
"field-group-type-implies-fields-is-required": {
|
||||
"anyOf": [
|
||||
{
|
||||
"not": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"anyOf": [{ "const": "group" }]
|
||||
}
|
||||
},
|
||||
"required": ["type"]
|
||||
}
|
||||
},
|
||||
{ "required": ["fields"] }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
/* eslint-disable */
|
||||
const defaultTheme = require("tailwindcss/defaultTheme");
|
||||
const { transform } = require("typescript");
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
@ -122,6 +123,7 @@ module.exports = {
|
||||
colorButtons: "repeat(auto-fill, minmax(4rem, 1fr))",
|
||||
platforms: "repeat(auto-fill, minmax(18rem, 1fr))",
|
||||
plugins: "repeat(auto-fill, minmax(20rem, 1fr))",
|
||||
radioGroup: "repeat(auto-fit, minmax(14rem, 1fr))",
|
||||
},
|
||||
gridTemplateRows: {
|
||||
admin: "40px 1fr",
|
||||
@ -162,6 +164,18 @@ module.exports = {
|
||||
zIndex: {
|
||||
60: 60,
|
||||
},
|
||||
keyframes: {
|
||||
"slight-pulse": {
|
||||
"0%": { transform: "scale(1)" },
|
||||
"60%": { transform: "scale(0.96)" },
|
||||
"75%": { transform: "scale(1.05)" },
|
||||
"95%": { transform: "scale(0.98)" },
|
||||
"100%": { transform: "scale(1)" },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"single-pulse": "slight-pulse 300ms linear 1",
|
||||
},
|
||||
},
|
||||
},
|
||||
variants: {},
|
||||
|
@ -74,19 +74,19 @@
|
||||
name="type"
|
||||
options="<?= esc(json_encode([
|
||||
[
|
||||
'label' => lang('Episode.form.type.full'),
|
||||
'value' => 'full',
|
||||
'hint' => lang('Episode.form.type.full_hint'),
|
||||
'label' => lang('Episode.form.type.full'),
|
||||
'value' => 'full',
|
||||
'description' => lang('Episode.form.type.full_description'),
|
||||
],
|
||||
[
|
||||
'label' => lang('Episode.form.type.trailer'),
|
||||
'value' => 'trailer',
|
||||
'hint' => lang('Episode.form.type.trailer_hint'),
|
||||
'label' => lang('Episode.form.type.trailer'),
|
||||
'value' => 'trailer',
|
||||
'description' => lang('Episode.form.type.trailer_description'),
|
||||
],
|
||||
[
|
||||
'label' => lang('Episode.form.type.bonus'),
|
||||
'value' => 'bonus',
|
||||
'hint' => lang('Episode.form.type.bonus_hint'),
|
||||
'label' => lang('Episode.form.type.bonus'),
|
||||
'value' => 'bonus',
|
||||
'description' => lang('Episode.form.type.bonus_description'),
|
||||
],
|
||||
])) ?>"
|
||||
isRequired="true"
|
||||
|
@ -78,19 +78,19 @@
|
||||
value="<?= $episode->type ?>"
|
||||
options="<?= esc(json_encode([
|
||||
[
|
||||
'label' => lang('Episode.form.type.full'),
|
||||
'value' => 'full',
|
||||
'hint' => lang('Episode.form.type.full_hint'),
|
||||
'label' => lang('Episode.form.type.full'),
|
||||
'value' => 'full',
|
||||
'description' => lang('Episode.form.type.full_description'),
|
||||
],
|
||||
[
|
||||
'label' => lang('Episode.form.type.trailer'),
|
||||
'value' => 'trailer',
|
||||
'hint' => lang('Episode.form.type.trailer_hint'),
|
||||
'label' => lang('Episode.form.type.trailer'),
|
||||
'value' => 'trailer',
|
||||
'description' => lang('Episode.form.type.trailer_description'),
|
||||
],
|
||||
[
|
||||
'label' => lang('Episode.form.type.bonus'),
|
||||
'value' => 'bonus',
|
||||
'hint' => lang('Episode.form.type.bonus_hint'),
|
||||
'label' => lang('Episode.form.type.bonus'),
|
||||
'value' => 'bonus',
|
||||
'description' => lang('Episode.form.type.bonus_description'),
|
||||
],
|
||||
])) ?>"
|
||||
isRequired="true"
|
||||
|
144
themes/cp_admin/plugins/_field.php
Normal file
144
themes/cp_admin/plugins/_field.php
Normal file
@ -0,0 +1,144 @@
|
||||
<?php switch ($type): case 'checkbox': ?>
|
||||
<x-Forms.Checkbox
|
||||
class="<?= $class ?>"
|
||||
name="<?= $name ?>"
|
||||
hint="<?= $hint ?>"
|
||||
helper="<?= $helper ?>"
|
||||
isChecked="<?= $value ? 'true' : 'false' ?>"
|
||||
><?= $label ?></x-Forms.Checkbox>
|
||||
<?php break;
|
||||
case 'toggler': ?>
|
||||
<x-Forms.Toggler
|
||||
class="<?= $class ?>"
|
||||
name="<?= $name ?>"
|
||||
hint="<?= $hint ?>"
|
||||
helper="<?= $helper ?>"
|
||||
isChecked="<?= $value ? 'true' : 'false' ?>"
|
||||
><?= $label ?></x-Forms.Toggler>
|
||||
<?php break;
|
||||
case 'radio-group': ?>
|
||||
<x-Forms.RadioGroup
|
||||
class="<?= $class ?>"
|
||||
name="<?= $name ?>"
|
||||
label="<?= $label ?>"
|
||||
hint="<?= $hint ?>"
|
||||
helper="<?= $helper ?>"
|
||||
options="<?= $options ?>"
|
||||
isRequired="<?= $optional ? 'false' : 'true' ?>"
|
||||
value="<?= $value ?>"
|
||||
/>
|
||||
<?php break;
|
||||
case 'select': ?>
|
||||
<x-Forms.Field
|
||||
as="Select"
|
||||
class="<?= $class ?>"
|
||||
name="<?= $name ?>"
|
||||
label="<?= $label ?>"
|
||||
hint="<?= $hint ?>"
|
||||
helper="<?= $helper ?>"
|
||||
options="<?= $options ?>"
|
||||
isRequired="<?= $optional ? 'false' : 'true' ?>"
|
||||
value="<?= $value ?>"
|
||||
/>
|
||||
<?php break;
|
||||
case 'select-multiple': ?>
|
||||
<x-Forms.Field
|
||||
as="SelectMulti"
|
||||
class="<?= $class ?>"
|
||||
name="<?= $name ?>"
|
||||
label="<?= $label ?>"
|
||||
hint="<?= $hint ?>"
|
||||
helper="<?= $helper ?>"
|
||||
options="<?= $options ?>"
|
||||
isRequired="<?= $optional ? 'false' : 'true' ?>"
|
||||
value="<?= esc(json_encode($value)) ?>"
|
||||
/>
|
||||
<?php break;
|
||||
case 'email': ?>
|
||||
<x-Forms.Field
|
||||
as="Input"
|
||||
class="<?= $class ?>"
|
||||
type="email"
|
||||
name="<?= $name ?>"
|
||||
label="<?= $label ?>"
|
||||
hint="<?= $hint ?>"
|
||||
helper="<?= $helper ?>"
|
||||
isRequired="<?= $optional ? 'false' : 'true' ?>"
|
||||
value="<?= $value ?>"
|
||||
/>
|
||||
<?php break;
|
||||
case 'url': ?>
|
||||
<x-Forms.Field
|
||||
as="Input"
|
||||
class="<?= $class ?>"
|
||||
type="url"
|
||||
placeholder="https://…"
|
||||
name="<?= $name ?>"
|
||||
label="<?= $label ?>"
|
||||
hint="<?= $hint ?>"
|
||||
helper="<?= $helper ?>"
|
||||
isRequired="<?= $optional ? 'false' : 'true' ?>"
|
||||
value="<?= $value ?>"
|
||||
/>
|
||||
<?php break;
|
||||
case 'number': ?>
|
||||
<x-Forms.Field
|
||||
as="Input"
|
||||
class="<?= $class ?>"
|
||||
type="number"
|
||||
name="<?= $name ?>"
|
||||
label="<?= $label ?>"
|
||||
hint="<?= $hint ?>"
|
||||
helper="<?= $helper ?>"
|
||||
isRequired="<?= $optional ? 'false' : 'true' ?>"
|
||||
value="<?= $value ?>"
|
||||
/>
|
||||
<?php break;
|
||||
case 'textarea': ?>
|
||||
<x-Forms.Field
|
||||
as="Textarea"
|
||||
class="<?= $class ?>"
|
||||
name="<?= $name ?>"
|
||||
label="<?= $label ?>"
|
||||
hint="<?= $hint ?>"
|
||||
helper="<?= $helper ?>"
|
||||
isRequired="<?= $optional ? 'false' : 'true' ?>"
|
||||
value="<?= $value ?>"
|
||||
/>
|
||||
<?php break;
|
||||
case 'markdown': ?>
|
||||
<x-Forms.Field
|
||||
as="MarkdownEditor"
|
||||
class="<?= $class ?>"
|
||||
name="<?= $name ?>"
|
||||
label="<?= $label ?>"
|
||||
hint="<?= $hint ?>"
|
||||
helper="<?= $helper ?>"
|
||||
isRequired="<?= $optional ? 'false' : 'true' ?>"
|
||||
value="<?= $value ?>"
|
||||
/>
|
||||
<?php break;
|
||||
case 'datetime': ?>
|
||||
<x-Forms.Field
|
||||
as="DatetimePicker"
|
||||
class="<?= $class ?>"
|
||||
name="<?= $name ?>"
|
||||
label="<?= $label ?>"
|
||||
hint="<?= $hint ?>"
|
||||
helper="<?= $helper ?>"
|
||||
isRequired="<?= $optional ? 'false' : 'true' ?>"
|
||||
value="<?= $value ?>"
|
||||
/>
|
||||
<?php break;
|
||||
default: ?>
|
||||
<x-Forms.Field
|
||||
as="Input"
|
||||
class="<?= $class ?>"
|
||||
name="<?= $name ?>"
|
||||
label="<?= $label ?>"
|
||||
hint="<?= $hint ?>"
|
||||
helper="<?= $helper ?>"
|
||||
isRequired="<?= $optional ? 'false' : 'true' ?>"
|
||||
value="<?= $value ?>"
|
||||
/>
|
||||
<?php endswitch; ?>
|
@ -2,138 +2,94 @@
|
||||
<?= csrf_field() ?>
|
||||
<?php $hasDatetime = false; ?>
|
||||
<?php foreach ($fields as $field): ?>
|
||||
<?php switch ($field->type): case 'checkbox': ?>
|
||||
<x-Forms.Checkbox
|
||||
name="<?= $field->key ?>"
|
||||
hint="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.hint', $plugin->getKey(), $type, $field->key), $field->hint)) ?>"
|
||||
isChecked="<?= get_plugin_setting($plugin->getKey(), $field->key, $context) ? 'true' : 'false' ?>"
|
||||
><?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.label', $plugin->getKey(), $type, $field->key), $field->label)) ?></x-Forms.Checkbox>
|
||||
<?php break;
|
||||
case 'toggler': ?>
|
||||
<x-Forms.Toggler
|
||||
name="<?= $field->key ?>"
|
||||
hint="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.hint', $plugin->getKey(), $type, $field->key), $field->hint)) ?>"
|
||||
isChecked="<?= get_plugin_setting($plugin->getKey(), $field->key, $context) ? 'true' : 'false' ?>"
|
||||
><?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.label', $plugin->getKey(), $type, $field->key), $field->label)) ?></x-Forms.Toggler>
|
||||
<?php break;
|
||||
case 'radio-group': ?>
|
||||
<x-Forms.RadioGroup
|
||||
name="<?= $field->key ?>"
|
||||
label="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.label', $plugin->getKey(), $type, $field->key), $field->label)) ?>"
|
||||
hint="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.hint', $plugin->getKey(), $type, $field->key), $field->hint)) ?>"
|
||||
helper="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.helper', $plugin->getKey(), $type, $field->key), $field->helper)) ?>"
|
||||
options="<?= esc(json_encode($field->getOptionsArray(sprintf('%s.settings.%s.%s.options', $plugin->getKey(), $type, $field->key)))) ?>"
|
||||
isRequired="<?= $field->optional ? 'false' : 'true' ?>"
|
||||
value="<?= get_plugin_setting($plugin->getKey(), $field->key, $context) ?>"
|
||||
/>
|
||||
<?php break;
|
||||
case 'select': ?>
|
||||
<x-Forms.Field
|
||||
as="Select"
|
||||
name="<?= $field->key ?>"
|
||||
label="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.label', $plugin->getKey(), $type, $field->key), $field->label)) ?>"
|
||||
hint="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.hint', $plugin->getKey(), $type, $field->key), $field->hint)) ?>"
|
||||
helper="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.helper', $plugin->getKey(), $type, $field->key), $field->helper)) ?>"
|
||||
options="<?= esc(json_encode($field->getOptionsArray(sprintf('%s.settings.%s.%s.options', $plugin->getKey(), $type, $field->key)))) ?>"
|
||||
isRequired="<?= $field->optional ? 'false' : 'true' ?>"
|
||||
value="<?= get_plugin_setting($plugin->getKey(), $field->key, $context) ?>"
|
||||
/>
|
||||
<?php break;
|
||||
case 'select-multiple': ?>
|
||||
<x-Forms.Field
|
||||
as="SelectMulti"
|
||||
name="<?= $field->key ?>"
|
||||
label="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.label', $plugin->getKey(), $type, $field->key), $field->label)) ?>"
|
||||
hint="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.hint', $plugin->getKey(), $type, $field->key), $field->hint)) ?>"
|
||||
helper="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.helper', $plugin->getKey(), $type, $field->key), $field->helper)) ?>"
|
||||
options="<?= esc(json_encode($field->getOptionsArray(sprintf('%s.settings.%s.%s.options', $plugin->getKey(), $type, $field->key)))) ?>"
|
||||
isRequired="<?= $field->optional ? 'false' : 'true' ?>"
|
||||
value="<?= esc(json_encode(get_plugin_setting($plugin->getKey(), $field->key, $context))) ?>"
|
||||
/>
|
||||
<?php break;
|
||||
case 'email': ?>
|
||||
<x-Forms.Field
|
||||
as="Input"
|
||||
type="email"
|
||||
name="<?= $field->key ?>"
|
||||
label="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.label', $plugin->getKey(), $type, $field->key), $field->label)) ?>"
|
||||
hint="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.hint', $plugin->getKey(), $type, $field->key), $field->hint)) ?>"
|
||||
helper="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.helper', $plugin->getKey(), $type, $field->key), $field->helper)) ?>"
|
||||
isRequired="<?= $field->optional ? 'false' : 'true' ?>"
|
||||
value="<?= get_plugin_setting($plugin->getKey(), $field->key, $context) ?>"
|
||||
/>
|
||||
<?php break;
|
||||
case 'url': ?>
|
||||
<x-Forms.Field
|
||||
as="Input"
|
||||
type="url"
|
||||
placeholder="https://…"
|
||||
name="<?= $field->key ?>"
|
||||
label="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.label', $plugin->getKey(), $type, $field->key), $field->label)) ?>"
|
||||
hint="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.hint', $plugin->getKey(), $type, $field->key), $field->hint)) ?>"
|
||||
helper="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.helper', $plugin->getKey(), $type, $field->key), $field->helper)) ?>"
|
||||
isRequired="<?= $field->optional ? 'false' : 'true' ?>"
|
||||
value="<?= get_plugin_setting($plugin->getKey(), $field->key, $context) ?>"
|
||||
/>
|
||||
<?php break;
|
||||
case 'number': ?>
|
||||
<x-Forms.Field
|
||||
as="Input"
|
||||
type="number"
|
||||
name="<?= $field->key ?>"
|
||||
label="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.label', $plugin->getKey(), $type, $field->key), $field->label)) ?>"
|
||||
hint="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.hint', $plugin->getKey(), $type, $field->key), $field->hint)) ?>"
|
||||
helper="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.helper', $plugin->getKey(), $type, $field->key), $field->helper)) ?>"
|
||||
isRequired="<?= $field->optional ? 'false' : 'true' ?>"
|
||||
value="<?= get_plugin_setting($plugin->getKey(), $field->key, $context) ?>"
|
||||
/>
|
||||
<?php break;
|
||||
case 'textarea': ?>
|
||||
<x-Forms.Field
|
||||
as="Textarea"
|
||||
name="<?= $field->key ?>"
|
||||
label="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.label', $plugin->getKey(), $type, $field->key), $field->label)) ?>"
|
||||
hint="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.hint', $plugin->getKey(), $type, $field->key), $field->hint)) ?>"
|
||||
helper="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.helper', $plugin->getKey(), $type, $field->key), $field->helper)) ?>"
|
||||
isRequired="<?= $field->optional ? 'false' : 'true' ?>"
|
||||
value="<?= get_plugin_setting($plugin->getKey(), $field->key, $context) ?>"
|
||||
/>
|
||||
<?php break;
|
||||
case 'markdown': ?>
|
||||
<x-Forms.Field
|
||||
as="MarkdownEditor"
|
||||
name="<?= $field->key ?>"
|
||||
label="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.label', $plugin->getKey(), $type, $field->key), $field->label)) ?>"
|
||||
hint="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.hint', $plugin->getKey(), $type, $field->key), $field->hint)) ?>"
|
||||
helper="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.helper', $plugin->getKey(), $type, $field->key), $field->helper)) ?>"
|
||||
isRequired="<?= $field->optional ? 'false' : 'true' ?>"
|
||||
value="<?= get_plugin_setting($plugin->getKey(), $field->key, $context) ?>"
|
||||
/>
|
||||
<?php break;
|
||||
case 'datetime':
|
||||
$hasDatetime = true ?>
|
||||
<x-Forms.Field
|
||||
as="DatetimePicker"
|
||||
name="<?= $field->key ?>"
|
||||
label="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.label', $plugin->getKey(), $type, $field->key), $field->label)) ?>"
|
||||
hint="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.hint', $plugin->getKey(), $type, $field->key), $field->hint)) ?>"
|
||||
helper="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.helper', $plugin->getKey(), $type, $field->key), $field->helper)) ?>"
|
||||
isRequired="<?= $field->optional ? 'false' : 'true' ?>"
|
||||
value="<?= get_plugin_setting($plugin->getKey(), $field->key, $context) ?>"
|
||||
/>
|
||||
<?php break;
|
||||
default: ?>
|
||||
<x-Forms.Field
|
||||
as="Input"
|
||||
name="<?= $field->key ?>"
|
||||
label="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.label', $plugin->getKey(), $type, $field->key), $field->label)) ?>"
|
||||
hint="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.hint', $plugin->getKey(), $type, $field->key), $field->hint)) ?>"
|
||||
helper="<?= esc($field->getTranslated(sprintf('%s.settings.%s.%s.helper', $plugin->getKey(), $type, $field->key), $field->helper)) ?>"
|
||||
isRequired="<?= $field->optional ? 'false' : 'true' ?>"
|
||||
value="<?= get_plugin_setting($plugin->getKey(), $field->key, $context) ?>"
|
||||
/>
|
||||
<?php endswitch; ?>
|
||||
|
||||
<?php if ($field->type === 'datetime') {
|
||||
$hasDatetime = true;
|
||||
} ?>
|
||||
<?php if ($field->multiple):
|
||||
if ($field->type === 'group'): ?>
|
||||
<div class="flex flex-col gap-4" data-field-array="<?= $field->key ?>">
|
||||
<fieldset class="flex flex-col gap-6 rounded" data-field-array-container="<?= $field->key ?>">
|
||||
<legend class="relative z-10 mb-4 font-bold text-heading-foreground font-display before:w-full before:absolute before:h-1/2 before:left-0 before:bottom-0 before:rounded-full before:bg-heading-background before:z-[-10] tracking-wide text-base"><?= $field->getTranslated($plugin->getKey(), 'label') ?></legend>
|
||||
<?php
|
||||
$fieldArrayValues = get_plugin_setting($plugin->getKey(), $field->key, $context) ?? [''];
|
||||
foreach ($fieldArrayValues as $index => $value): ?>
|
||||
<fieldset class="relative flex flex-col border border-subtle p-4 rounded-tl-none rounded-md gap-2 bg-base" data-field-array-item="<?= $index ?>">
|
||||
<legend class="absolute font-mono left-0 -top-px -ml-6 rounded-l-full rounded-r-none w-6 text-xs h-6 inline-flex items-center justify-center font-semibold border border-subtle bg-base"><span class="sr-only"><?= $field->getTranslated($plugin->getKey(), 'label') ?></span> <span data-field-array-number><?= $index + 1 ?></span></legend>
|
||||
<?php foreach ($field->fields as $subfield): ?>
|
||||
<?= view('plugins/_field', [
|
||||
'class' => 'flex-1',
|
||||
'type' => $subfield->type,
|
||||
'name' => sprintf('%s[%s][%s]', $field->key, $index, $subfield->key),
|
||||
'label' => $subfield->getTranslated($plugin->getKey(), 'label'),
|
||||
'hint' => $subfield->getTranslated($plugin->getKey(), 'hint'),
|
||||
'value' => $value[$subfield->key] ?? '',
|
||||
'helper' => $subfield->getTranslated($plugin->getKey(), 'helper'),
|
||||
'options' => esc(json_encode($subfield->getOptionsArray($plugin->getKey()))),
|
||||
'optional' => $subfield->optional,
|
||||
]) ?>
|
||||
<?php endforeach; ?>
|
||||
<x-IconButton variant="danger" glyph="delete-bin-fill" data-field-array-delete="<?= $index ?>" class="absolute right-0 top-0 -mt-4 -mr-4"><?= lang('Common.forms.fieldArray.remove') ?></x-IconButton>
|
||||
</fieldset>
|
||||
<?php endforeach; ?>
|
||||
</fieldset>
|
||||
<x-Button iconLeft="add-fill" data-field-array-add="<?= $field->key ?>" variant="secondary" type="button" class="mt-2"><?= lang('Common.forms.fieldArray.add') ?></x-Button>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="flex flex-col gap-4" data-field-array="<?= $field->key ?>">
|
||||
<fieldset class="flex flex-col gap-2" data-field-array-container="<?= $field->key ?>">
|
||||
<?php $fieldArrayValue = get_plugin_setting($plugin->getKey(), $field->key, $context) ?? [''];
|
||||
foreach ($fieldArrayValue as $index => $value): ?>
|
||||
<div class="relative flex items-end" data-field-array-item="<?= $index ?>">
|
||||
<span class="self-start mr-1 -ml-5 w-4 rtl text-sm before:content-['.']" data-field-array-number style="direction:rtl"><?= $index + 1 ?></span>
|
||||
<?= view('plugins/_field', [
|
||||
'class' => 'flex-1',
|
||||
'type' => $field->type,
|
||||
'name' => sprintf('%s[%s]', $field->key, $index),
|
||||
'label' => $field->getTranslated($plugin->getKey(), 'label'),
|
||||
'hint' => $field->getTranslated($plugin->getKey(), 'hint'),
|
||||
'value' => $value,
|
||||
'helper' => $field->getTranslated($plugin->getKey(), 'helper'),
|
||||
'options' => esc(json_encode($field->getOptionsArray($plugin->getKey()))),
|
||||
'optional' => $field->optional,
|
||||
]) ?>
|
||||
<x-IconButton variant="danger" glyph="delete-bin-fill" data-field-array-delete="<?= $index ?>" type="button" class="mb-2 ml-2"><?= lang('Common.forms.fieldArray.remove') ?></x-IconButton>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</fieldset>
|
||||
<x-Button iconLeft="add-fill" data-field-array-add="<?= $field->key ?>" variant="secondary" type="button" class="mt-2"><?= lang('Common.forms.fieldArray.add') ?></x-Button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php elseif ($field->type === 'group'):
|
||||
$value = get_plugin_setting($plugin->getKey(), $field->key, $context); ?>
|
||||
<fieldset class="flex flex-col border border-subtle p-4 rounded-tl-none rounded-md gap-2 bg-base">
|
||||
<legend class="relative z-10 font-bold text-heading-foreground font-display before:w-full before:absolute before:h-1/2 before:left-0 before:bottom-0 before:rounded-full before:bg-heading-background before:z-[-10] tracking-wide text-base"><?= $field->getTranslated($plugin->getKey(), 'label') ?></legend>
|
||||
<?php foreach ($field->fields as $subfield): ?>
|
||||
<?= view('plugins/_field', [
|
||||
'class' => 'flex-1',
|
||||
'type' => $subfield->type,
|
||||
'name' => sprintf('%s[%s]', $field->key, $subfield->key),
|
||||
'label' => $subfield->getTranslated($plugin->getKey(), 'label'),
|
||||
'hint' => $subfield->getTranslated($plugin->getKey(), 'hint'),
|
||||
'value' => $value[$subfield->key] ?? '',
|
||||
'helper' => $subfield->getTranslated($plugin->getKey(), 'helper'),
|
||||
'options' => esc(json_encode($subfield->getOptionsArray($plugin->getKey()))),
|
||||
'optional' => $subfield->optional,
|
||||
]) ?>
|
||||
<?php endforeach; ?>
|
||||
</fieldset>
|
||||
<?php else: ?>
|
||||
<?= view('plugins/_field', [
|
||||
'class' => '',
|
||||
'type' => $field->type,
|
||||
'name' => $field->key,
|
||||
'label' => $field->getTranslated($plugin->getKey(), 'label'),
|
||||
'hint' => $field->getTranslated($plugin->getKey(), 'hint'),
|
||||
'value' => get_plugin_setting($plugin->getKey(), $field->key, $context),
|
||||
'helper' => $field->getTranslated($plugin->getKey(), 'helper'),
|
||||
'options' => esc(json_encode($field->getOptionsArray($plugin->getKey()))),
|
||||
'optional' => $field->optional,
|
||||
]) ?>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if ($hasDatetime): ?>
|
||||
|
@ -46,14 +46,14 @@
|
||||
name="type"
|
||||
options="<?= esc(json_encode([
|
||||
[
|
||||
'label' => lang('Podcast.form.type.episodic'),
|
||||
'value' => 'episodic',
|
||||
'hint' => lang('Podcast.form.type.episodic_hint'),
|
||||
'label' => lang('Podcast.form.type.episodic'),
|
||||
'value' => 'episodic',
|
||||
'description' => lang('Podcast.form.type.episodic_description'),
|
||||
],
|
||||
[
|
||||
'label' => lang('Podcast.form.type.serial'),
|
||||
'value' => 'serial',
|
||||
'hint' => lang('Podcast.form.type.serial_hint'),
|
||||
'label' => lang('Podcast.form.type.serial'),
|
||||
'value' => 'serial',
|
||||
'description' => lang('Podcast.form.type.serial_description'),
|
||||
],
|
||||
])) ?>"
|
||||
isRequired="true"
|
||||
@ -64,19 +64,19 @@
|
||||
name="medium"
|
||||
options="<?= esc(json_encode([
|
||||
[
|
||||
'label' => lang('Podcast.form.medium.podcast'),
|
||||
'value' => 'podcast',
|
||||
'hint' => lang('Podcast.form.medium.podcast_hint'),
|
||||
'label' => lang('Podcast.form.medium.podcast'),
|
||||
'value' => 'podcast',
|
||||
'description' => lang('Podcast.form.medium.podcast_description'),
|
||||
],
|
||||
[
|
||||
'label' => lang('Podcast.form.medium.music'),
|
||||
'value' => 'music',
|
||||
'hint' => lang('Podcast.form.medium.music_hint'),
|
||||
'label' => lang('Podcast.form.medium.music'),
|
||||
'value' => 'music',
|
||||
'description' => lang('Podcast.form.medium.music_description'),
|
||||
],
|
||||
[
|
||||
'label' => lang('Podcast.form.medium.audiobook'),
|
||||
'value' => 'audiobook',
|
||||
'hint' => lang('Podcast.form.medium.audiobook_hint'),
|
||||
'label' => lang('Podcast.form.medium.audiobook'),
|
||||
'value' => 'audiobook',
|
||||
'description' => lang('Podcast.form.medium.audiobook_description'),
|
||||
],
|
||||
])) ?>"
|
||||
isRequired="true"
|
||||
|
@ -70,14 +70,14 @@
|
||||
value="<?= $podcast->type ?>"
|
||||
options="<?= esc(json_encode([
|
||||
[
|
||||
'label' => lang('Podcast.form.type.episodic'),
|
||||
'value' => 'episodic',
|
||||
'hint' => lang('Podcast.form.type.episodic_hint'),
|
||||
'label' => lang('Podcast.form.type.episodic'),
|
||||
'value' => 'episodic',
|
||||
'description' => lang('Podcast.form.type.episodic_description'),
|
||||
],
|
||||
[
|
||||
'label' => lang('Podcast.form.type.serial'),
|
||||
'value' => 'serial',
|
||||
'hint' => lang('Podcast.form.type.serial_hint'),
|
||||
'label' => lang('Podcast.form.type.serial'),
|
||||
'value' => 'serial',
|
||||
'description' => lang('Podcast.form.type.serial_description'),
|
||||
],
|
||||
])) ?>"
|
||||
isRequired="true"
|
||||
@ -89,19 +89,19 @@
|
||||
value="<?= $podcast->medium ?>"
|
||||
options="<?= esc(json_encode([
|
||||
[
|
||||
'label' => lang('Podcast.form.medium.podcast'),
|
||||
'value' => 'podcast',
|
||||
'hint' => lang('Podcast.form.medium.podcast_hint'),
|
||||
'label' => lang('Podcast.form.medium.podcast'),
|
||||
'value' => 'podcast',
|
||||
'description' => lang('Podcast.form.medium.podcast_description'),
|
||||
],
|
||||
[
|
||||
'label' => lang('Podcast.form.medium.music'),
|
||||
'value' => 'music',
|
||||
'hint' => lang('Podcast.form.medium.music_hint'),
|
||||
'label' => lang('Podcast.form.medium.music'),
|
||||
'value' => 'music',
|
||||
'description' => lang('Podcast.form.medium.music_description'),
|
||||
],
|
||||
[
|
||||
'label' => lang('Podcast.form.medium.audiobook'),
|
||||
'value' => 'audiobook',
|
||||
'hint' => lang('Podcast.form.medium.audiobook_hint'),
|
||||
'label' => lang('Podcast.form.medium.audiobook'),
|
||||
'value' => 'audiobook',
|
||||
'description' => lang('Podcast.form.medium.audiobook_description'),
|
||||
],
|
||||
])) ?>"
|
||||
isRequired="true"
|
||||
|
Loading…
x
Reference in New Issue
Block a user