mirror of
https://code.castopod.org/adaures/castopod.git
synced 2024-09-08 03:39:20 +02:00
docs(plugins): fill up rest of manifest and hooks reference + creating a plugin
This commit is contained in:
parent
cc6495dc7c
commit
e417d45b14
@ -12,15 +12,15 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.7.0",
|
||||
"@astrojs/starlight": "^0.22.4",
|
||||
"@astrojs/starlight-tailwind": "^2.0.2",
|
||||
"@astrojs/starlight": "^0.24.0",
|
||||
"@astrojs/starlight-tailwind": "^2.0.3",
|
||||
"@astrojs/tailwind": "^5.1.0",
|
||||
"@fontsource/inter": "^5.0.18",
|
||||
"@fontsource/rubik": "^5.0.20",
|
||||
"astro": "^4.8.6",
|
||||
"astro": "^4.10.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"cssnano": "^7.0.1",
|
||||
"postcss-preset-env": "^9.5.13",
|
||||
"cssnano": "^7.0.2",
|
||||
"postcss-preset-env": "^9.5.14",
|
||||
"sharp": "^0.33.4",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^5.4.5"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2,24 +2,29 @@
|
||||
title: Creating a Plugin
|
||||
---
|
||||
|
||||
import { FileTree, Steps } from "@astrojs/starlight/components";
|
||||
import { FileTree, Steps, Badge } from "@astrojs/starlight/components";
|
||||
|
||||
In order to get started, you first need to
|
||||
[setup your Castopod dev environment](https://code.castopod.org/adaures/castopod/-/blob/develop/CONTRIBUTING-DEV.md).
|
||||
|
||||
## Using the create command
|
||||
## 1. Create the plugin folder
|
||||
|
||||
To quickly get you started, you can create a plugin using the following CLI
|
||||
command:
|
||||
You'll first need to create your [plugin folder](./#plugin-folder-structure) in
|
||||
the `plugins/` directory.
|
||||
|
||||
### Using the create command <Badge text="Recommended" size="small" />
|
||||
|
||||
To quickly get you started, you can have a folder generated for you using the
|
||||
following CLI command:
|
||||
|
||||
```sh
|
||||
php spark plugins:create
|
||||
```
|
||||
|
||||
👉 Follow the CLI instructions: you will be prompted for metadata and hooks
|
||||
definitions to generate the [plugin folder](./#plugin-folder-structure) for you.
|
||||
👉 You will be prompted for metadata and hooks usage to have a skeleton plugin
|
||||
project generated for you!
|
||||
|
||||
## Manual setup
|
||||
### Manual setup
|
||||
|
||||
<Steps>
|
||||
1. create a plugin folder inside a vendor directory
|
||||
@ -54,3 +59,58 @@ definitions to generate the [plugin folder](./#plugin-folder-structure) for you.
|
||||
</FileTree>
|
||||
|
||||
</Steps>
|
||||
|
||||
## 2. Build your plugin
|
||||
|
||||
Now that your plugin folder is set, you can start working on your Plugin's logic
|
||||
by implementing [the hooks](./hooks) needed.
|
||||
|
||||
### Settings forms
|
||||
|
||||
You can prompt users for data through settings forms.
|
||||
|
||||
These forms can be built declaratively using the
|
||||
[settings attribute](./manifest#settings) in your manifest.
|
||||
|
||||
```json
|
||||
// manifest.json
|
||||
{
|
||||
"settings": {
|
||||
"general": {
|
||||
"field-key": {
|
||||
"type": "text",
|
||||
"label": "Enter a text"
|
||||
}
|
||||
},
|
||||
"podcast": {
|
||||
"field-key": {
|
||||
"type": "text",
|
||||
"label": "Enter a text for this podcast"
|
||||
}
|
||||
},
|
||||
"episode": {
|
||||
"field-key": {
|
||||
"type": "type",
|
||||
"label": "Enter a text for this episode"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This example will generate settings forms at 3 levels:
|
||||
|
||||
- `general`: a general form to prompt data to be used by the plugin
|
||||
- `podcast`: a form for each podcast to prompt for podcast specific data
|
||||
- `episode`: a form for each episode to prompt for episode specific data
|
||||
|
||||
The data can then be accessed in the Plugin class methods via helper methods
|
||||
taking in the field key:
|
||||
|
||||
```php
|
||||
$this->getGeneralSetting('field-key');
|
||||
|
||||
$this->getPodcastSetting($podcast->id, 'field-key');
|
||||
|
||||
$this->getEpisodeSetting($episode->id, 'field-key');
|
||||
```
|
||||
|
@ -1,3 +0,0 @@
|
||||
---
|
||||
title: BasePlugin
|
||||
---
|
@ -2,8 +2,8 @@
|
||||
title: Hooks reference
|
||||
---
|
||||
|
||||
Hooks are methods that live in the Plugin class, they are executed in parts of
|
||||
the Castopod codebase.
|
||||
Hooks are methods of the Plugin class, they are executed in parts of the
|
||||
Castopod codebase.
|
||||
|
||||
## List
|
||||
|
||||
@ -17,6 +17,11 @@ the Castopod codebase.
|
||||
|
||||
### rssBeforeChannel
|
||||
|
||||
This hook is executed just before rendering the `<channel>` tag in the Podcast
|
||||
RSS feed using the given Podcast object.
|
||||
|
||||
Here is a good place to alter the Podcast object.
|
||||
|
||||
```php
|
||||
public function rssBeforeChannel(Podcast $podcast): void
|
||||
{
|
||||
@ -26,8 +31,13 @@ public function rssBeforeChannel(Podcast $podcast): void
|
||||
|
||||
### rssAfterChannel
|
||||
|
||||
This hook is executed after rendering all of the `<channel>` tags in the Podcast
|
||||
RSS feed.
|
||||
|
||||
Here is a good place to add new tags to the generated channel.
|
||||
|
||||
```php
|
||||
public function rssAfterChannel(Podcast $podcast, SimpleRSSElement $rss): void
|
||||
public function rssAfterChannel(Podcast $podcast, SimpleRSSElement $channel): void
|
||||
{
|
||||
// …
|
||||
}
|
||||
@ -35,6 +45,11 @@ public function rssAfterChannel(Podcast $podcast, SimpleRSSElement $rss): void
|
||||
|
||||
### rssBeforeItem
|
||||
|
||||
This hook is executed before rendering an `<item>` tag in the Podcast RSS feed
|
||||
using the given Episode object.
|
||||
|
||||
Here is a good place to alter the Episode object.
|
||||
|
||||
```php
|
||||
public function rssBeforeItem(Episode $episode): void
|
||||
{
|
||||
@ -44,8 +59,13 @@ public function rssBeforeItem(Episode $episode): void
|
||||
|
||||
### rssAfterItem
|
||||
|
||||
This hook is executed after rendering an `<item>`'s tags in the Podcast RSS
|
||||
feed.
|
||||
|
||||
Here is a good place to add new tags to the generated item.
|
||||
|
||||
```php
|
||||
public function rssAfterItem(Epsiode $episode, SimpleRSSElement $rss): void
|
||||
public function rssAfterItem(Epsiode $episode, SimpleRSSElement $item): void
|
||||
{
|
||||
// …
|
||||
}
|
||||
@ -53,6 +73,11 @@ public function rssAfterItem(Epsiode $episode, SimpleRSSElement $rss): void
|
||||
|
||||
### siteHead
|
||||
|
||||
This hook is executed in the public pages' `<head>` tag.
|
||||
|
||||
This is a good place to add meta tags and third-party scripts to Castopod's
|
||||
public pages.
|
||||
|
||||
```php
|
||||
public function siteHead(): void
|
||||
{
|
||||
|
@ -2,7 +2,7 @@
|
||||
title: Castopod Plugins
|
||||
---
|
||||
|
||||
import { FileTree, Aside } from "@astrojs/starlight/components";
|
||||
import { FileTree, Aside, Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
Plugins are ways to extend Castopod's core features.
|
||||
|
||||
@ -16,13 +16,13 @@ Plugins are ways to extend Castopod's core features.
|
||||
- fr.json
|
||||
- …
|
||||
- icon.svg
|
||||
- [manifest.json](./manifest) // required
|
||||
- [Plugin.php](#plugin-class) // required
|
||||
- manifest.json // required
|
||||
- Plugin.php // required
|
||||
- README.md
|
||||
|
||||
</FileTree>
|
||||
|
||||
Plugins reside in the `plugins` folder under a **vendor** folder, ie. the
|
||||
Plugins reside in the `plugins/` directory under a `vendor/` folder, ie. the
|
||||
organisation or person who authored the plugin.
|
||||
|
||||
<FileTree>
|
||||
@ -35,15 +35,16 @@ organisation or person who authored the plugin.
|
||||
|
||||
</FileTree>
|
||||
|
||||
### manifest.json (required)
|
||||
### Plugin manifest (required)
|
||||
|
||||
The plugin manifest is a JSON file containing your plugin's metadata and
|
||||
permissions.
|
||||
The plugin manifest is a JSON file containing the plugin's metadata and
|
||||
declarations.
|
||||
|
||||
This file will determine whether a plugin is valid or not. The minimal required
|
||||
data being:
|
||||
|
||||
```json
|
||||
// manifest.json
|
||||
{
|
||||
"name": "acme/hello-world",
|
||||
"version": "1.0.0"
|
||||
@ -52,12 +53,12 @@ data being:
|
||||
|
||||
Checkout the [manifest.json reference](./manifest).
|
||||
|
||||
<h3 id="plugin-class">Plugin class (required)</h3>
|
||||
### Plugin class (required)
|
||||
|
||||
This is where your plugin's logic will live.
|
||||
This is where the plugin's logic lives.
|
||||
|
||||
The Plugin class must extend Castopod's BasePlugin class and implement one or
|
||||
multiple [Hooks](./hooks) (methods).
|
||||
The Plugin class extends Castopod's BasePlugin class and implements one or more
|
||||
[Hooks](./hooks) (methods) intended to be run throughout Castopod's codebase.
|
||||
|
||||
```php
|
||||
// Plugin.php
|
||||
@ -69,7 +70,11 @@ use Modules\Plugins\Core\BasePlugin;
|
||||
|
||||
class AcmeHelloWorldPlugin extends BasePlugin
|
||||
{
|
||||
// …
|
||||
// this rssBeforeChannel method is a Hook
|
||||
public function rssBeforeChannel(Podcast $podcast): void
|
||||
{
|
||||
// …
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -85,14 +90,14 @@ For example, a plugin living under the `acme/hello-world` folder must be named
|
||||
|
||||
</Aside>
|
||||
|
||||
### README.md
|
||||
### Plugin README
|
||||
|
||||
The `README.md` file is loaded into the plugin's view page for the user to
|
||||
read.
|
||||
The `README.md` file is loaded into the plugin's view page for the user to read
|
||||
through.
|
||||
It should be used for any additional information to help guide the user in using
|
||||
the plugin.
|
||||
|
||||
### icon.svg
|
||||
### Plugin icon
|
||||
|
||||
The plugin icon is displayed next to its title, it is an SVG file intended to
|
||||
give a graphical representation of the plugin.
|
||||
@ -101,8 +106,8 @@ The icon should be squared, and be legible in a 64px by 64px circle.
|
||||
|
||||
### Internationalization (i18n)
|
||||
|
||||
Translation strings live under the `i18n` folder. Translation files are JSON
|
||||
files named as locale keys:
|
||||
Plugins can be translated. Translation strings live inside the `i18n` folder.
|
||||
Translation files are JSON files named as locale keys:
|
||||
|
||||
<FileTree>
|
||||
|
||||
@ -118,16 +123,45 @@ Supported locales are:
|
||||
`br`,`ca`,`de`,`en`,`es`,`fr`,`nn-no`,`pl`,`pt-br`,`sr-latn`,`zh-hans`.
|
||||
|
||||
The translation strings allow you to translate the title, description and
|
||||
settings keys.
|
||||
settings keys (ie. labels, hints, helpers, etc.).
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "Hello, World!",
|
||||
"description": "A Castopod plugin to greet the world!",
|
||||
"settings": {
|
||||
"general": {},
|
||||
"podcast": {},
|
||||
"episode": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
<Tabs>
|
||||
<TabItem label="English">
|
||||
```json
|
||||
// i18n/en.json
|
||||
{
|
||||
"title": "Hello, World!",
|
||||
"description": "A Castopod plugin to greet the world!",
|
||||
"settings": {
|
||||
"general": {
|
||||
"field-key": {
|
||||
"label": "Enter a text",
|
||||
"hint": "You can enter any type of character."
|
||||
}
|
||||
},
|
||||
"podcast": {},
|
||||
"episode": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem label="French">
|
||||
```json
|
||||
// i18n/fr.json
|
||||
{
|
||||
"title": "Bonjour, le Monde !",
|
||||
"description": "Un plugin castopod pour saluer le monde !",
|
||||
"settings": {
|
||||
"general": {
|
||||
"field-key": {
|
||||
"label": "Saisissez un texte",
|
||||
"hint": "Vous pouvez saisir n'importe quel type de caractère."
|
||||
}
|
||||
},
|
||||
"podcast": {},
|
||||
"episode": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
@ -7,7 +7,14 @@ a JSON file.
|
||||
|
||||
### name (required)
|
||||
|
||||
The plugin name, including 'vendor-name/' prefix.
|
||||
The plugin name, including 'vendor-name/' prefix. Examples:
|
||||
|
||||
- acme/hello-world
|
||||
- adaures/click
|
||||
|
||||
The name must be lowercase and consist of words separated by `-`, `.` or `_`.
|
||||
The complete name should match
|
||||
`^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9]([_.-]?[a-z0-9]+)*$`.
|
||||
|
||||
### version (required)
|
||||
|
||||
@ -15,8 +22,8 @@ The plugin's semantic version (eg. 1.0.0) - see https://semver.org/
|
||||
|
||||
### description
|
||||
|
||||
The plugin's description. This helps people discover your plugin as it's listed
|
||||
in repositories
|
||||
The plugin's description. This helps people discover your plugin when listed in
|
||||
repositories.
|
||||
|
||||
### authors
|
||||
|
||||
@ -25,7 +32,7 @@ a required "name" field and optional "email" and "url" fields:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Jean D'eau",
|
||||
"name": "Jean Deau",
|
||||
"email": "jean.deau@example.com",
|
||||
"url": "https://example.com/"
|
||||
}
|
||||
@ -34,7 +41,7 @@ a required "name" field and optional "email" and "url" fields:
|
||||
Or you can shorten the object into a single string:
|
||||
|
||||
```json
|
||||
"Jean D'eau <jean.deau@example.com> (https://example.com/)"
|
||||
"Jean Deau <jean.deau@example.com> (https://example.com/)"
|
||||
```
|
||||
|
||||
### homepage
|
||||
@ -43,17 +50,79 @@ The URL to the project homepage.
|
||||
|
||||
### license
|
||||
|
||||
You should specify a license for your plugin so that people know how they are
|
||||
permitted to use it, and any restrictions you're placing on it.
|
||||
Specify a license for your plugin so that people know how they are permitted to
|
||||
use it, and any restrictions you're placing on it.
|
||||
|
||||
### private
|
||||
|
||||
Whether or not to publish the plugin in public directories. If set to `true`,
|
||||
directories should refuse to publish the plugin.
|
||||
|
||||
### keywords
|
||||
|
||||
Array of strings to help your plugin get discovered when listed in repositories.
|
||||
|
||||
### hooks
|
||||
|
||||
List of hooks used by the plugin. If the hook is not specified, Castopod will
|
||||
not run it.
|
||||
|
||||
### settings
|
||||
|
||||
Declare settings forms for persisting user data. The plugin's settings forms can
|
||||
be declared at three levels: `general`, `podcast`, and `episode`.
|
||||
|
||||
Each level accepts one or more fields, identified by a key.
|
||||
|
||||
```json
|
||||
{
|
||||
"settings": {
|
||||
"general": { // general settings form
|
||||
"field-key": {
|
||||
"type": "text", // default field type: a text input
|
||||
"label": "Enter a text"
|
||||
},
|
||||
…
|
||||
},
|
||||
"podcast": {…}, // settings form for each podcast
|
||||
"episode": {…}, // settings form for each episode
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `general`, `podcast`, and `episode` settings are of `Fields` object with
|
||||
each property being a field key and the value being a `Field` object.
|
||||
|
||||
#### 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. |
|
||||
|
||||
#### Options object
|
||||
|
||||
The `Options` object properties are option keys and the value is an `Option`.
|
||||
|
||||
##### Option object
|
||||
|
||||
| Property | Type | Note |
|
||||
| ------------------ | -------- | ---------------------------- |
|
||||
| `label` (required) | `string` | Can be translated (see i18n) |
|
||||
| `hint` | `string` | Can be translated (see i18n) |
|
||||
|
||||
### files
|
||||
|
||||
Array of file patterns that describes the entries to be included when your
|
||||
plugin is installed.
|
||||
|
||||
### repository
|
||||
|
||||
Repository where the plugin's code lives. Helpful for people who want to
|
||||
contribute.
|
||||
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Modules\Plugins\Manifest;
|
||||
|
||||
use Override;
|
||||
|
||||
/**
|
||||
* @property string $key
|
||||
* @property 'text'|'email'|'url'|'markdown'|'number'|'switch' $type
|
||||
@ -45,6 +47,22 @@ class Field extends ManifestObject
|
||||
*/
|
||||
protected array $options = [];
|
||||
|
||||
#[Override]
|
||||
public function loadData(array $data): void
|
||||
{
|
||||
if (array_key_exists('options', $data)) {
|
||||
$newOptions = [];
|
||||
foreach ($data['options'] as $key => $option) {
|
||||
$option['value'] = $key;
|
||||
$newOptions[] = $option;
|
||||
}
|
||||
|
||||
$data['options'] = $newOptions;
|
||||
}
|
||||
|
||||
parent::loadData($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{label:string,value:string,hint:string}[]
|
||||
*/
|
||||
|
@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Modules\Plugins\Manifest;
|
||||
|
||||
use Override;
|
||||
|
||||
class Fields extends ManifestObject
|
||||
{
|
||||
#[Override]
|
||||
public function loadData(array $data): void
|
||||
{
|
||||
dd($data);
|
||||
}
|
||||
}
|
@ -195,12 +195,11 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"options": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/option"
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[A-Za-z0-9]+[\\w\\-\\:\\.]*$": { "$ref": "#/$defs/option" }
|
||||
},
|
||||
"minItems": 1,
|
||||
"uniqueItems": true
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["label"],
|
||||
@ -215,14 +214,11 @@
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"hint": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["label", "value"],
|
||||
"required": ["label"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"field-multiple-implies-options-is-required": {
|
||||
|
@ -14,129 +14,107 @@
|
||||
"keywords": ["seo", "analytics"],
|
||||
"hooks": ["rssAfterChannel"],
|
||||
"settings": {
|
||||
"general": [
|
||||
{
|
||||
"general": {
|
||||
"name": {
|
||||
"type": "radio-group",
|
||||
"key": "name",
|
||||
"label": "Name",
|
||||
"options": [
|
||||
{ "label": "Foo", "value": "foo", "hint": "This is a hint." },
|
||||
{ "label": "Bar", "value": "bar" }
|
||||
]
|
||||
"options": {
|
||||
"foo": { "label": "Foo", "hint": "This is a hint." },
|
||||
"bar": { "label": "Bar" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"email": {
|
||||
"type": "email",
|
||||
"key": "email",
|
||||
"label": "Email"
|
||||
},
|
||||
{
|
||||
"url": {
|
||||
"type": "url",
|
||||
"key": "url",
|
||||
"label": "Your website URL"
|
||||
},
|
||||
{
|
||||
"toggler": {
|
||||
"type": "toggler",
|
||||
"key": "toggler",
|
||||
"label": "Toggle this?"
|
||||
},
|
||||
{
|
||||
"number": {
|
||||
"type": "number",
|
||||
"key": "number",
|
||||
"label": "Number"
|
||||
},
|
||||
{
|
||||
"datetime": {
|
||||
"type": "datetime",
|
||||
"key": "datetime",
|
||||
"label": "Enter a date",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"select": {
|
||||
"type": "select",
|
||||
"key": "select",
|
||||
"label": "Select something",
|
||||
"options": [
|
||||
{
|
||||
"label": "Foo",
|
||||
"value": "foo"
|
||||
"options": {
|
||||
"foo": {
|
||||
"label": "Foo"
|
||||
},
|
||||
{
|
||||
"label": "Bar",
|
||||
"value": "bar"
|
||||
"bar": {
|
||||
"label": "Bar"
|
||||
},
|
||||
{
|
||||
"label": "Baz",
|
||||
"value": "baz"
|
||||
"baz": {
|
||||
"label": "Baz"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"select-multiple": {
|
||||
"type": "select-multiple",
|
||||
"key": "select-multiple",
|
||||
"label": "Select multiple things",
|
||||
"options": [
|
||||
{
|
||||
"label": "Foo",
|
||||
"value": "foo"
|
||||
"options": {
|
||||
"foo": {
|
||||
"label": "Foo"
|
||||
},
|
||||
{
|
||||
"label": "Bar",
|
||||
"value": "bar"
|
||||
"bar": {
|
||||
"label": "Bar"
|
||||
},
|
||||
{
|
||||
"label": "Baz",
|
||||
"value": "baz"
|
||||
"baz": {
|
||||
"label": "Baz"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"radio-group": {
|
||||
"type": "radio-group",
|
||||
"key": "radio-group",
|
||||
"label": "Radio Group",
|
||||
"helper": "This is a helper.",
|
||||
"options": [
|
||||
{
|
||||
"label": "Foo",
|
||||
"value": "foo"
|
||||
"options": {
|
||||
"foo": {
|
||||
"label": "Foo"
|
||||
},
|
||||
{
|
||||
"label": "Bar",
|
||||
"value": "bar"
|
||||
"bar": {
|
||||
"label": "Bar"
|
||||
},
|
||||
{
|
||||
"label": "Baz",
|
||||
"value": "baz"
|
||||
"baz": {
|
||||
"label": "Baz"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"texting": {
|
||||
"type": "textarea",
|
||||
"key": "texting",
|
||||
"label": "Your text",
|
||||
"hint": "This is a hint."
|
||||
},
|
||||
{
|
||||
"hello": {
|
||||
"type": "markdown",
|
||||
"key": "hello",
|
||||
"label": "Name Podcast",
|
||||
"hint": "This is a hint.",
|
||||
"optional": true
|
||||
}
|
||||
],
|
||||
"podcast": [
|
||||
{
|
||||
},
|
||||
"podcast": {
|
||||
"name": {
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name Podcast",
|
||||
"hint": "This is a hint."
|
||||
}
|
||||
],
|
||||
"episode": [
|
||||
{
|
||||
},
|
||||
"episode": {
|
||||
"name": {
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name Episode",
|
||||
"helper": "This is a helper."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,11 +18,11 @@
|
||||
"name": {
|
||||
"type": "radio-group",
|
||||
"label": "Name",
|
||||
"options": [
|
||||
{ "label": "Foo", "value": "foo", "hint": "This is a hint." },
|
||||
{ "label": "Bar", "value": "bar" },
|
||||
{ "label": "Baz", "value": "baz" }
|
||||
]
|
||||
"options": {
|
||||
"foo": { "label": "Foo", "hint": "This is a hint." },
|
||||
"bar": { "label": "Bar" },
|
||||
"baz": { "label": "Baz" }
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"type": "email",
|
||||
@ -48,57 +48,48 @@
|
||||
"select": {
|
||||
"type": "select",
|
||||
"label": "Select something",
|
||||
"options": [
|
||||
{
|
||||
"label": "Foo",
|
||||
"value": "foo"
|
||||
"options": {
|
||||
"foo": {
|
||||
"label": "Foo"
|
||||
},
|
||||
{
|
||||
"label": "Bar",
|
||||
"value": "bar"
|
||||
"bar": {
|
||||
"label": "Bar"
|
||||
},
|
||||
{
|
||||
"label": "Baz",
|
||||
"value": "baz"
|
||||
"baz": {
|
||||
"label": "Baz"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"select-multiple": {
|
||||
"type": "select-multiple",
|
||||
"label": "Select multiple things",
|
||||
"options": [
|
||||
{
|
||||
"label": "Foo",
|
||||
"value": "foo"
|
||||
"options": {
|
||||
"foo": {
|
||||
"label": "Foo"
|
||||
},
|
||||
{
|
||||
"label": "Bar",
|
||||
"value": "bar"
|
||||
"bar": {
|
||||
"label": "Bar"
|
||||
},
|
||||
{
|
||||
"label": "Baz",
|
||||
"value": "baz"
|
||||
"baz": {
|
||||
"label": "Baz"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"radio-group": {
|
||||
"type": "radio-group",
|
||||
"label": "Radio Group",
|
||||
"helper": "This is a helper.",
|
||||
"options": [
|
||||
{
|
||||
"label": "Foo",
|
||||
"value": "foo"
|
||||
"options": {
|
||||
"foo": {
|
||||
"label": "Foo"
|
||||
},
|
||||
{
|
||||
"label": "Bar",
|
||||
"value": "bar"
|
||||
"bar": {
|
||||
"label": "Bar"
|
||||
},
|
||||
{
|
||||
"label": "Baz",
|
||||
"value": "baz"
|
||||
"baz": {
|
||||
"label": "Baz"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"textarea": {
|
||||
"type": "textarea",
|
||||
|
Loading…
Reference in New Issue
Block a user