Compare commits
5 Commits
f7e950694e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ad2a6f87b | ||
|
|
2afc2cdc34 | ||
|
|
4835ff9467 | ||
|
|
49b52b0f9f | ||
|
|
76221ee1d4 |
@@ -3,11 +3,11 @@ title: "Campos editables del builder"
|
|||||||
tags: [builder, twig, html, modules]
|
tags: [builder, twig, html, modules]
|
||||||
load_priority: 80
|
load_priority: 80
|
||||||
load_when: [always]
|
load_when: [always]
|
||||||
summary: "Atributos data-field-* (textfield, headfield, link, upload, list, multiv2, checkbox), agrupar campos en pestañas con data-field-group, c-if/c-for/c-class, c-form, componentes built-in del builder Acai."
|
summary: "Atributos data-field-* (textfield, headfield, link, upload, list, multiv2, colors, colorpicker), agrupar campos en pestañas con data-field-group, mostrar u ocultar campos y pestañas segun otro campo con data-field-show y data-field-group-show, c-if/c-for/c-class, c-form, componentes built-in del builder Acai."
|
||||||
---
|
---
|
||||||
# Builder Fields — Campos editables del index-base.tpl
|
# Builder Fields — Campos editables del index-base.tpl
|
||||||
|
|
||||||
Este documento define los campos editables que el usuario rellena desde el panel del builder de Acai. Cubre el atributo `data-field-type` con todos sus tipos (`textfield`, `headfield`, `textbox`, `wysiwyg`, `link`, `upload`, `uploadMulti`, `list`, `multiv2`, `checkbox`, `colorpicker`), la regla `data-field-label` → nombre de variable, los atributos Acai (`c-if`, `c-else`, `c-for`, `c-class`, `c-hidden`, `c-required`), el tag `<set>`, la inclusión de módulos, los formularios `c-form` y los componentes built-in. Léelo antes de crear o modificar cualquier `index-base.tpl`.
|
Este documento define los campos editables que el usuario rellena desde el panel del builder de Acai. Cubre el atributo `data-field-type` con todos sus tipos (`textfield`, `headfield`, `textbox`, `wysiwyg`, `link`, `upload`, `uploadMulti`, `list`, `multiv2`, `colors`, `colorpicker`, `corners`, `ratio`), la regla `data-field-label` → nombre de variable, el reparto en pestañas (`data-field-group`) y su visibilidad condicional (`data-field-show`, `data-field-group-show`), los atributos Acai (`c-if`, `c-else`, `c-for`, `c-class`, `c-hidden`, `c-required`), el tag `<set>`, la inclusión de módulos, los formularios `c-form` y los componentes built-in. Léelo antes de crear o modificar cualquier `index-base.tpl`.
|
||||||
|
|
||||||
## Reglas de nomenclatura de variables
|
## Reglas de nomenclatura de variables
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ Reglas obligatorias:
|
|||||||
| `list` (fijo) | `<div data-list-options="...">` | Valor seleccionado |
|
| `list` (fijo) | `<div data-list-options="...">` | Valor seleccionado |
|
||||||
| `list` (tabla) | `<div data-list-table="...">` | `num` del registro |
|
| `list` (tabla) | `<div data-list-table="...">` | `num` del registro |
|
||||||
| `multiv2` | `<li>` wrapper | Array de objetos repetibles |
|
| `multiv2` | `<li>` wrapper | Array de objetos repetibles |
|
||||||
| `checkbox` | `<div>` o `<input>` | `1` o `0` (número) |
|
| `colors` | `<div data-field-colors="fondo,titulo">` | Objeto de N colores por nombre: `{{ colores.fondo }}` |
|
||||||
| `colorpicker` | `<div>` | Hex color string |
|
| `colorpicker` | `<div>` | Hex color string |
|
||||||
|
|
||||||
### textfield
|
### textfield
|
||||||
@@ -210,13 +210,54 @@ Uso en Twig:
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
```
|
```
|
||||||
|
|
||||||
### checkbox
|
### colors — paleta de colores
|
||||||
|
|
||||||
Devuelve `1` o `0` (número), nunca `true`/`false`.
|
Un solo campo que agrupa VARIOS colores. Los nombres de cada color se declaran en
|
||||||
|
`data-field-colors`, separados por comas:
|
||||||
|
|
||||||
### colorpicker
|
```html
|
||||||
|
<div c-hidden="true">
|
||||||
|
<div data-field-type="colors"
|
||||||
|
data-field-label="Colores"
|
||||||
|
data-field-colors="fondo,titulo,boton"></div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
Devuelve un string hexadecimal (`#ff0000`). Almacenado en config-vars (no en `builder_custom`).
|
El valor guardado es un JSON con pares nombre/valor. Cada color puede ser sólido o un
|
||||||
|
gradiente lineal:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"fondo": "#ffffff", "titulo": "#111111", "boton": "linear-gradient(90deg, #aaa, #000)"}
|
||||||
|
```
|
||||||
|
|
||||||
|
En Twig se accede a cada color por su nombre: `{{ colores.fondo }}`.
|
||||||
|
|
||||||
|
### colorpicker — un solo color
|
||||||
|
|
||||||
|
Cuando solo necesitas UN color, no una paleta. El valor es un string hexadecimal plano:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div c-hidden="true">
|
||||||
|
<div data-field-type="colorpicker" data-field-label="Color de fondo"></div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Valor guardado: `#ff0000`. En Twig: `{{ colordefondo }}`.
|
||||||
|
|
||||||
|
Usa `colorpicker` para un color suelto y `colors` cuando el módulo tenga varios: `colors`
|
||||||
|
gasta UNA sola variable para N colores, mientras que N `colorpicker` gastan N.
|
||||||
|
|
||||||
|
### corners — radios de esquina
|
||||||
|
|
||||||
|
Selector de radio de borde en escala Tailwind. Mismo patrón que `colors`.
|
||||||
|
|
||||||
|
### ratio — proporción
|
||||||
|
|
||||||
|
Selector de proporción de imagen (16/9, 4/3, 1/1...).
|
||||||
|
|
||||||
|
> Para un valor booleano usa `list` con dos opciones: el builder no tiene un tipo de casilla.
|
||||||
|
> (No lo confundas con el tipo `checkbox` de los campos de TABLA del CMS, que sí existe y se
|
||||||
|
> documenta en `05-tables-and-fields.md`. Son dos vocabularios distintos.)
|
||||||
|
|
||||||
## Agrupar campos en pestañas (`data-field-group`)
|
## Agrupar campos en pestañas (`data-field-group`)
|
||||||
|
|
||||||
@@ -253,6 +294,54 @@ Recomendación de uso:
|
|||||||
- Reparto habitual: **Contenido** (textos e imágenes), **Estilos** (colores, alineación, bordes), **Ajustes** (opciones de comportamiento, límites, enlaces).
|
- Reparto habitual: **Contenido** (textos e imágenes), **Estilos** (colores, alineación, bordes), **Ajustes** (opciones de comportamiento, límites, enlaces).
|
||||||
- Mantén los nombres de grupo consistentes entre módulos y en español. Son literales: `Estilos` y `estilos` generan dos pestañas distintas.
|
- Mantén los nombres de grupo consistentes entre módulos y en español. Son literales: `Estilos` y `estilos` generan dos pestañas distintas.
|
||||||
|
|
||||||
|
## Mostrar u ocultar campos y pestañas (`data-field-show`)
|
||||||
|
|
||||||
|
Un campo o una pestaña entera pueden depender del valor de otro campo del mismo módulo. Se declara con dos atributos, que viajan al `builder.json` por el mismo mecanismo genérico que `data-field-group` (`customDataField.show` y `customDataField['group-show']`):
|
||||||
|
|
||||||
|
- `data-field-show="campo=valor"` — en el elemento del campo que se quiere condicionar.
|
||||||
|
- `data-field-group-show="campo=valor"` — en **una** var del grupo; condiciona la pestaña completa.
|
||||||
|
|
||||||
|
Gramática:
|
||||||
|
|
||||||
|
| Condición | Se muestra cuando |
|
||||||
|
|-----------|-------------------|
|
||||||
|
| `modo=1` | el valor es `1` |
|
||||||
|
| `modo=1,2` | el valor es `1` o `2` |
|
||||||
|
| `modo=` | el campo está **vacío** — es la clave de la primera opción de todo `list` |
|
||||||
|
| `modo!=1` | el valor NO es `1` |
|
||||||
|
| `modo=1;otro=2` | se cumplen ambas (AND) |
|
||||||
|
|
||||||
|
El campo se referencia por su **nombre de variable**, no por su label: se aplican las [reglas de nomenclatura](#reglas-de-nomenclatura-de-variables), así que `Mostrar Estilos` se referencia como `mostrarestilos` y `Título` como `ttulo` (los acentos se borran, no se transliteran). Los valores no pueden contener coma ni punto y coma.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div c-hidden="true">
|
||||||
|
<!-- Pestaña completa: "Estilos" solo aparece si el usuario activa el switch -->
|
||||||
|
<div data-field-type="list" data-field-label="Mostrar Estilos" data-list-options="|No,1|Si"></div>
|
||||||
|
<div data-field-type="textfield"
|
||||||
|
data-field-label="Texto de Estilos"
|
||||||
|
data-field-group="Estilos"
|
||||||
|
data-field-group-show="mostrarestilos=1"></div>
|
||||||
|
|
||||||
|
<!-- Campo a campo: cada opción del list muestra su propio campo -->
|
||||||
|
<div data-field-type="list"
|
||||||
|
data-field-label="Modo Modulo"
|
||||||
|
data-list-options="|Opcion 1,1|Opcion 2,2|Opcion 3"
|
||||||
|
data-field-group="Contenido"></div>
|
||||||
|
<div data-field-type="textfield" data-field-label="Opcion 1" data-field-group="Contenido" data-field-show="modomodulo="></div>
|
||||||
|
<div data-field-type="textfield" data-field-label="Opcion 2" data-field-group="Contenido" data-field-show="modomodulo=1"></div>
|
||||||
|
<div data-field-type="textfield" data-field-label="Opcion 3" data-field-group="Contenido" data-field-show="modomodulo=2"></div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Comportamiento del panel:
|
||||||
|
- **Ocultar no borra.** El valor sigue guardado y sigue llegando al Twig; si el usuario vuelve a mostrar el campo, lo escrito sigue ahí.
|
||||||
|
- **Por eso la plantilla debe repetir la condición con `c-if`.** Ocultar el campo en el panel NO lo quita de la web: si `Texto de Estilos` no debe pintarse cuando `mostrarestilos` está a `0`, el `index-base.tpl` necesita su propio `c-if="mostrarestilos = '1'"`.
|
||||||
|
- Una pestaña se oculta sola cuando todos sus campos han quedado ocultos por su `show`; en la mayoría de casos basta con condicionar los campos y no hace falta `data-field-group-show`.
|
||||||
|
- Si varias vars del mismo grupo declaran `data-field-group-show`, **gana la primera** y las demás se ignoran.
|
||||||
|
- Si una condición apunta a un campo que no existe (typo, o un label renombrado que cambió el nombre de variable), el campo **se muestra igualmente** y el aviso queda en la consola del navegador. Nunca desaparece en silencio.
|
||||||
|
- Dentro de `multiv2` la condición se evalúa contra los valores **de ese item**, no contra los del módulo.
|
||||||
|
- En modo traducción las condiciones se evalúan contra los valores del **idioma base**.
|
||||||
|
|
||||||
## Atributos Acai
|
## Atributos Acai
|
||||||
|
|
||||||
### `c-if` — Renderizado condicional
|
### `c-if` — Renderizado condicional
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ title: "Reglas inmutables y cheat-sheet de tipos"
|
|||||||
tags: [reference, rules, cheat]
|
tags: [reference, rules, cheat]
|
||||||
load_priority: 90
|
load_priority: 90
|
||||||
load_when: [cheatsheet]
|
load_when: [cheatsheet]
|
||||||
summary: "Reglas no negociables (cms_, num, _num, upload arrays, c-if/{% if %}), tipos de builder field, atributos Acai, filtros Twig, formato de datos para insert/update, errores comunes."
|
summary: "Reglas no negociables (cms_, num, _num, upload arrays, c-if/{% if %}), tipos de builder field, pestañas y visibilidad condicional de campos (data-field-group, data-field-show), atributos Acai, filtros Twig, formato de datos para insert/update, errores comunes."
|
||||||
---
|
---
|
||||||
# Reglas inmutables y cheat-sheet
|
# Reglas inmutables y cheat-sheet
|
||||||
|
|
||||||
@@ -42,11 +42,13 @@ Resumen ejecutable de reglas críticas, tipos de campo, filtros y formatos de da
|
|||||||
| `list` (fijo) | `<div data-list-options="...">` | Valor seleccionado |
|
| `list` (fijo) | `<div data-list-options="...">` | Valor seleccionado |
|
||||||
| `list` (tabla) | `<div data-list-table="...">` | `num` del registro |
|
| `list` (tabla) | `<div data-list-table="...">` | `num` del registro |
|
||||||
| `multiv2` | `<li>` wrapper | Array de objetos |
|
| `multiv2` | `<li>` wrapper | Array de objetos |
|
||||||
| `checkbox` | `<input>` o `<div>` | `1` / `0` |
|
| `colors` | `<div data-field-colors="fondo,titulo">` | Objeto de N colores por nombre |
|
||||||
| `colorpicker` | `<div>` | Hex color |
|
| `colorpicker` | `<div>` | Hex color |
|
||||||
|
|
||||||
Pestañas en el panel del módulo: `data-field-group="Estilos"` en el elemento del campo (sin group → pestaña "Principal").
|
Pestañas en el panel del módulo: `data-field-group="Estilos"` en el elemento del campo (sin group → pestaña "Principal").
|
||||||
|
|
||||||
|
Visibilidad condicional: `data-field-show="modo=1"` en el campo, y `data-field-group-show="modo=1"` en **una** var del grupo para condicionar la pestaña entera. Se referencia el NOMBRE DE VARIABLE, no el label. `modo=` significa vacío (clave de la primera opción de todo `list`), `,` es OR, `!=` niega y `;` encadena condiciones (AND).
|
||||||
|
|
||||||
## Atributos Acai
|
## Atributos Acai
|
||||||
|
|
||||||
| Atributo | Uso | Ejemplo |
|
| Atributo | Uso | Ejemplo |
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ Definiciones cortas de los términos que aparecen en docs y prompts. Si te pierd
|
|||||||
|
|
||||||
**`c-form`** — atributo que convierte un `<form>` en un formulario que persiste a una tabla del CMS. Sintaxis: `<c-form tableName="'contacto'" captcha="true">`. Se renderiza como form HTML con submit a un endpoint Acai.
|
**`c-form`** — atributo que convierte un `<form>` en un formulario que persiste a una tabla del CMS. Sintaxis: `<c-form tableName="'contacto'" captcha="true">`. Se renderiza como form HTML con submit a un endpoint Acai.
|
||||||
|
|
||||||
**`data-field-*`** — familia de atributos que marca un elemento como editable en el builder visual. Tipos: `textfield`, `headfield`, `textbox`, `wysiwyg`, `link`, `upload`, `uploadMulti`, `list`, `multiv2`, `checkbox`, `colorpicker`.
|
**`data-field-*`** — familia de atributos que marca un elemento como editable en el builder visual. Tipos: `textfield`, `headfield`, `textbox`, `wysiwyg`, `link`, `upload`, `uploadMulti`, `list`, `multiv2`, `colors`, `colorpicker`.
|
||||||
|
|
||||||
**`c-if`, `c-for`, `c-class`, `c-hidden`, `c-required`** — atributos de lógica visual. **`c-if` usa un solo `=`** (`c-if="x = 1"`), Twig `{% if %}` usa **doble** `==`.
|
**`c-if`, `c-for`, `c-class`, `c-hidden`, `c-required`** — atributos de lógica visual. **`c-if` usa un solo `=`** (`c-if="x = 1"`), Twig `{% if %}` usa **doble** `==`.
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { withAuth, getSessionCredentials, getApiClient, getCommonParams } from "../../auth/index.js";
|
import { withAuth } from "../../auth/index.js";
|
||||||
import { handleToolError, validateRequired, handleApiResponse } from "../helpers/errorHandler.js";
|
import { handleToolError, validateRequired } from "../helpers/errorHandler.js";
|
||||||
import { withAuthParams } from "../helpers/authSchema.js";
|
import { withAuthParams } from "../helpers/authSchema.js";
|
||||||
|
import { pythonPost } from "../helpers/pythonServerClient.js";
|
||||||
|
import { getCurrentProjectInfo } from "../files/helpers.js";
|
||||||
|
|
||||||
|
// Antes esta tool llamaba a `action_ws=setStaticVars`, que hacia un
|
||||||
|
// file_put_contents del builder.json ENTERO en la web para cambiar una sola
|
||||||
|
// clave. Eso se saltaba el bloqueo de escritura de builder.json y, si caia una
|
||||||
|
// compilacion entre su lectura y su escritura, devolvia el mapeo var->columna
|
||||||
|
// anterior encima del recien generado (contenido rotado en todas las paginas
|
||||||
|
// que usan el modulo). Ahora delega en el endpoint quirurgico de Forge, que
|
||||||
|
// escribe SOLO las claves de su allowlist.
|
||||||
|
|
||||||
export function registerSetModuleExampleDataTool(server) {
|
export function registerSetModuleExampleDataTool(server) {
|
||||||
server.tool(
|
server.tool(
|
||||||
@@ -10,6 +20,8 @@ export function registerSetModuleExampleDataTool(server) {
|
|||||||
|
|
||||||
Reglas críticas:
|
Reglas críticas:
|
||||||
- Uploads SIEMPRE como [{ urlPath: "..." }] (nunca strings ni objetos sueltos).
|
- Uploads SIEMPRE como [{ urlPath: "..." }] (nunca strings ni objetos sueltos).
|
||||||
|
- 'colors' como STRING con un JSON de pares nombre/color, usando los nombres declarados en data-field-colors: "{\\"fondo\\":\\"#ffffff\\",\\"titulo\\":\\"#111111\\"}". Cada color admite hex o linear-gradient(...).
|
||||||
|
- 'colorpicker' como un hex plano ("#ff0000"), no como objeto.
|
||||||
- 'multiv2' como array con al menos 2 items para que el preview se vea representativo.
|
- 'multiv2' como array con al menos 2 items para que el preview se vea representativo.
|
||||||
- Los nombres de variables se derivan de 'data-field-label' (minúsculas, sin espacios ni acentos).
|
- Los nombres de variables se derivan de 'data-field-label' (minúsculas, sin espacios ni acentos).
|
||||||
- Para URLs de imagen usa 'generate_image' o un placeholder (e.g. https://placehold.co/800x600).
|
- Para URLs de imagen usa 'generate_image' o un placeholder (e.g. https://placehold.co/800x600).
|
||||||
@@ -50,42 +62,36 @@ Si dudas del formato exacto, lee 'read_doc({ name: "01-builder-fields" })'.`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const credentials = await getSessionCredentials(extra.sessionId);
|
console.error(`[set_module_example_data] Module ID: ${moduleId}, vars: ${Object.keys(exampleData).length}`);
|
||||||
const client = await getApiClient(extra.sessionId);
|
|
||||||
|
|
||||||
// Log data for debugging
|
const { projectSlug } = getCurrentProjectInfo();
|
||||||
console.error(`[set_module_example_data] Module ID: ${moduleId}`);
|
const result = await pythonPost("/api/modules/update-metadata", {
|
||||||
console.error(`[set_module_example_data] Module Schema:`, JSON.stringify(moduleSchema, null, 2));
|
project: projectSlug,
|
||||||
console.error(`[set_module_example_data] Example Data:`, JSON.stringify(exampleData, null, 2));
|
module: moduleId,
|
||||||
|
|
||||||
// Prepare payload for setStaticVars action
|
|
||||||
const payload = await getCommonParams(extra.sessionId, {
|
|
||||||
action_ws: "setStaticVars",
|
|
||||||
moduleId: moduleId,
|
|
||||||
staticVars: exampleData,
|
staticVars: exampleData,
|
||||||
schema: moduleSchema
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.error(`[set_module_example_data] Full Payload:`, JSON.stringify(payload, null, 2));
|
if (!result?.success) {
|
||||||
|
return {
|
||||||
// Send to viewer_functions
|
content: [{
|
||||||
const response = await client.post("/cms/lib/viewer_functions.php", payload);
|
type: "text",
|
||||||
|
text: JSON.stringify({
|
||||||
console.error(`[set_module_example_data] Response:`, JSON.stringify(response.data, null, 2));
|
success: false,
|
||||||
|
error: result?.error || "Could not set module example data",
|
||||||
// Check for API errors in response
|
}),
|
||||||
const apiError = handleApiResponse(response.data, 'set_module_example_data');
|
}],
|
||||||
if (apiError) return apiError;
|
isError: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: [{
|
content: [{
|
||||||
type: "text", text: JSON.stringify({
|
type: "text", text: JSON.stringify({
|
||||||
success: true,
|
success: true,
|
||||||
message: `Example data set successfully for module '${moduleId}'`,
|
message: `Example data set successfully for module '${moduleId}'`,
|
||||||
moduleId: moduleId,
|
moduleId: result.module || moduleId,
|
||||||
dataCount: Object.keys(exampleData).length,
|
dataCount: Object.keys(exampleData).length,
|
||||||
schemaVarsCount: moduleSchema?.codeVars ? Object.keys(moduleSchema.codeVars).length : 0,
|
schemaVarsCount: moduleSchema?.codeVars ? Object.keys(moduleSchema.codeVars).length : 0,
|
||||||
response: response.data
|
|
||||||
}, null, 2)
|
}, null, 2)
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user