feat(16.2-01): compile, decode and load plugin field types in cabana
- compileFieldTypes builds a per-boot field type registry (alias, core and cross-plugin collisions, element prefix, assets, option keys) passed down the compile functions; settings and relation forms refuse plugin aliases - fields.yaml accepts a plugin alias with the general keys plus the type's options (typeOptions, element, translatable in the schema); CheckOptions runs at boot; CompileFormWithTypes for plugin tests - save decodes plugin values inside the transaction (FormFieldError is a 422, other errors opaque 500) and answers meta.notices (FieldNotice) only when a type reported something; show and save serve values through Load - GET /field-types lists the types with versioned asset URLs; OpenAPI, schema.d.ts, README and docs/backend/field-types.md updated
This commit is contained in:
@@ -680,6 +680,24 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"cabana.FieldNotice": {
|
||||
"properties": {
|
||||
"field": {
|
||||
"type": "string"
|
||||
},
|
||||
"locale": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"field",
|
||||
"message"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"cabana.FieldPreset": {
|
||||
"properties": {
|
||||
"field": {
|
||||
@@ -695,6 +713,57 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"cabana.FieldTypeAssets": {
|
||||
"properties": {
|
||||
"element": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"scripts": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"styles": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"translatable": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"element",
|
||||
"name",
|
||||
"scripts",
|
||||
"styles",
|
||||
"translatable"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"cabana.FieldTypesResponse": {
|
||||
"properties": {
|
||||
"data": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/cabana.FieldTypeAssets"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/cabana.SuccessMeta"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"data",
|
||||
"meta"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"cabana.FileItem": {
|
||||
"properties": {
|
||||
"content_type": {
|
||||
@@ -802,6 +871,10 @@
|
||||
"displayFormat": {
|
||||
"type": "string"
|
||||
},
|
||||
"element": {
|
||||
"description": "Element is the custom-element tag of a field whose type a plugin\nregisters (pact.HasFormFieldTypes); the admin renders it for the field.",
|
||||
"type": "string"
|
||||
},
|
||||
"emptyOption": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -935,6 +1008,10 @@
|
||||
],
|
||||
"description": "ThumbOptions carries the preview thumbnail mode."
|
||||
},
|
||||
"translatable": {
|
||||
"description": "Translatable is true when a plugin field type's value is a map of\ncontent locale to string.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"twelveHour": {
|
||||
"description": "TwelveHour shows a 12-hour clock.",
|
||||
"type": "boolean"
|
||||
@@ -942,6 +1019,11 @@
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"typeOptions": {
|
||||
"additionalProperties": {},
|
||||
"description": "TypeOptions are a plugin field type's own fields.yaml keys and values;\nthe admin hands them to the element as JSON.",
|
||||
"type": "object"
|
||||
},
|
||||
"useCaption": {
|
||||
"description": "UseCaption lets the admin edit each file's title and description.",
|
||||
"type": "boolean"
|
||||
@@ -1643,6 +1725,12 @@
|
||||
"type": "array"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"notices": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/cabana.FieldNotice"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -2496,6 +2584,62 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/field-types": {
|
||||
"get": {
|
||||
"description": "Every admin form field type a plugin registers (pact.HasFormFieldTypes), sorted by name, with the URLs of its scripts and stylesheets (each with a ?v= content hash). The admin loads every script once after sign-in and keeps every stylesheet enabled on all pages; a form field whose `element` is set renders that custom element. An empty list when no plugin registers a type. Any backend session may call it.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/cabana.FieldTypesResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "OK"
|
||||
},
|
||||
"401": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/cabana.ErrorEnvelope"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/cabana.ErrorEnvelope"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Forbidden"
|
||||
},
|
||||
"404": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/cabana.ErrorEnvelope"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Not Found"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"BackendBearer": []
|
||||
}
|
||||
],
|
||||
"summary": "Plugin field types",
|
||||
"tags": [
|
||||
"admin"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/lang": {
|
||||
"get": {
|
||||
"description": "Every backend::lang key as CLDR plural forms for the Accept-Language locale, over the fallback locale's keys. Public: the login screen loads it before signing in. meta.locale is the locale the bundle resolved to.",
|
||||
@@ -3079,7 +3223,7 @@
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"description": "Relation fields are sent by field name with ids ({\"genre\": 3, \"artists\": [4, 9]}); the response carries the same shape plus meta.labels.",
|
||||
"description": "Relation fields are sent by field name with ids ({\"genre\": 3, \"artists\": [4, 9]}); the response carries the same shape plus meta.labels. A plugin field type's value is decoded by its type inside the save; when a type reports what it changed, meta.notices lists the localized messages per field (and content locale), and the key is absent otherwise.",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Vendor",
|
||||
@@ -3757,7 +3901,7 @@
|
||||
},
|
||||
"/{vendor}/{plugin}/{controller}/schema/form": {
|
||||
"get": {
|
||||
"description": "The form of a controller, localized. `preview` is present when config_form.yaml declares a preview block: the form then has a read-only preview screen, which shows the fields whose context allows preview, the record actions and, when preview.headerPartial is set, that partial as a status hint. A `type: password` field and every other field the controller lists as virtual is sent in a save body and never has a value in a record response. `preset` on a text or mltext field names the text or mltext field it follows on the create form (type slug or exact; an mltext pair follows per locale) until the administrator edits it. A `type: permissioneditor` field carries `permissionOptions`, the permissions the controller offers the requesting administrator; its value in a record response and in a save body is an object of permission code to integer (radio mode 1 or -1, checkbox mode 1). A `type: relation` field over a protected foreign key is `readOnly` unless its contract declares the key writable.",
|
||||
"description": "The form of a controller, localized. `preview` is present when config_form.yaml declares a preview block: the form then has a read-only preview screen, which shows the fields whose context allows preview, the record actions and, when preview.headerPartial is set, that partial as a status hint. A `type: password` field and every other field the controller lists as virtual is sent in a save body and never has a value in a record response. `preset` on a text or mltext field names the text or mltext field it follows on the create form (type slug or exact; an mltext pair follows per locale) until the administrator edits it. A `type: permissioneditor` field carries `permissionOptions`, the permissions the controller offers the requesting administrator; its value in a record response and in a save body is an object of permission code to integer (radio mode 1 or -1, checkbox mode 1). A `type: relation` field over a protected foreign key is `readOnly` unless its contract declares the key writable. A field whose type a plugin registers carries `element` (the custom-element tag the admin renders), `translatable` (its value is an object of content locale to string) and `typeOptions` (the type's own fields.yaml keys).",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Vendor",
|
||||
@@ -4442,6 +4586,7 @@
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"description": "A plugin field type's value is decoded by its type inside the save. When a type reports what it changed, meta.notices lists the localized messages per field (and content locale), and the key is absent otherwise.",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Vendor",
|
||||
|
||||
Reference in New Issue
Block a user