diff --git a/CLAUDE.md b/CLAUDE.md index 8007051..1062cd6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ This is an Acai CMS website project. Follow these instructions when working with - The site runs in Docker, typically at **http://localhost:8080** - You can make HTTP requests to test pages, APIs, or form submissions -- If you need to inspect the live site, use browser tools or HTTP requests to localhost:8080 +- If you need to inspect the live site, use browser tools (Playwright MCP) or HTTP requests to localhost:8080 ## Project Structure @@ -15,10 +15,12 @@ This is an Acai CMS website project. Follow these instructions when working with ├── template/estandar/ │ ├── modulos/ # Builder modules (visual components) │ │ └── / -│ │ ├── index-base.tpl # Twig template (source) -│ │ ├── index.tpl # Compiled template (auto-generated, do NOT edit) +│ │ ├── index-base.tpl # Twig template (source — EDIT THIS) │ │ ├── style.css # Module styles │ │ └── script.js # Module JavaScript +│ │ ├── index.tpl # Compiled (auto-generated, do NOT edit) +│ │ ├── index-twig.tpl # Compiled (auto-generated, do NOT edit) +│ │ └── builder.json # Compiled builder vars (auto-generated, do NOT edit) │ ├── css/ # Global CSS │ └── js/ # Global JavaScript ├── hooks/ # PHP hooks (server-side logic) @@ -38,17 +40,28 @@ This is an Acai CMS website project. Follow these instructions when working with ## Key Concepts ### Modules (`template/estandar/modulos/`) -Visual components that the site builder uses. Each module is a self-contained unit with its own template (Twig), CSS, and JS. Modules are placed on pages via the drag-and-drop builder. +Visual components that the site builder uses. Each module is a self-contained unit with its own template (Twig + Acai attributes), CSS, and JS. Modules are placed on pages via the drag-and-drop builder. The editable file is always `index-base.tpl`. + +- Include other modules: `` +- Each module instance gets a unique `section_id` variable for anchors/scoping +- Use `interno` variable to detect CMS editor mode vs public view See [docs/modular-system.md](docs/modular-system.md) for detailed rules. ### General Sections -Reusable layout blocks (header, footer, sidebars) that appear across multiple pages. They use the same Twig engine as modules but serve a structural purpose. +Database-backed templates (headers, footers, record views) that use the `thisrecord` variable to access record fields. They use the same Twig + Acai attribute engine as modules. + +- Upload fields return arrays: `thisrecord.image[0].urlPath` +- Foreign keys use `_num` suffix: `category_num` See [docs/modular-system.md](docs/modular-system.md) for details. ### Hooks (`hooks/`) -PHP files that execute server-side logic at specific points: before/after page load, on form submission, on API calls, scheduled tasks, etc. Hooks extend the CMS behavior without modifying the core. +PHP files that execute server-side logic. Triggered by: +- Twig filter: `'hooks/module_id/' | hook({param: value})` +- HTML tag: `` +- JavaScript: `CmsApi.hook('/hooks/module_id/', {param: value}, callback)` +- Form action: via `c-form` attribute See [docs/hooks-and-api.md](docs/hooks-and-api.md) for usage. @@ -57,25 +70,41 @@ See [docs/hooks-and-api.md](docs/hooks-and-api.md) for usage. When the site is running in Docker, you can connect to the database: - **Host:** `127.0.0.1` -- **Port:** Check `.docker/.env` or `docker-compose.yml` for the mapped port (usually 3307+) +- **Port:** Check `.docker/docker-compose.yml` for the mapped port (usually 3307+) - **Credentials:** Read from `.docker/.env`: - `DB_USERNAME` - `DB_PASSWORD` - `DB_DATABASE` -You can also exec into the container: ```bash docker exec -it dw--db mysql -u root -p ``` +**Important:** Table names in CmsApi/Twig do NOT use the `cms_` prefix. The primary key is always `num`, never `id`. + ## Acai Core (web-base) The project workspace contains only the **customization layer** (modules, hooks, schemas, uploads). The CMS core (routing, rendering engine, admin panel, APIs) lives in a separate directory called **web-base** that is mounted as a Docker volume. -If you need to understand core behavior (Twig filters, CmsApi methods, routing, etc.), the web-base path can be obtained from the plugin settings. Do NOT modify web-base files — they are shared across all projects. +The web-base path can be obtained via: `GET http://localhost:9090/api/web-base-path` + +Do NOT modify web-base files — they are shared across all projects. + +## Critical Rules + +1. Only edit `index-base.tpl` in modules — `index.tpl`, `index-twig.tpl`, and `builder.json` are auto-generated +2. Use Twig **filters** (with `|`), never Twig functions +3. Table names without `cms_` prefix everywhere +4. Primary key is `num`, never `id` +5. Upload fields are arrays — access with `[0].urlPath` +6. Tailwind CSS as primary styling, custom CSS scoped with BEM when needed +7. Twig concatenation uses `~` operator: `'value=' ~ variable` +8. `enlace` (link) fields already include slashes ## Documentation -- [docs/modular-system.md](docs/modular-system.md) — Modules, general sections, Twig syntax, builder vars, custom filters -- [docs/hooks-and-api.md](docs/hooks-and-api.md) — PHP hooks, CmsApi, CocoDB, database operations +- [docs/modular-system.md](docs/modular-system.md) — Modules, general sections, global variables - [docs/builder-fields.md](docs/builder-fields.md) — Builder field types, c-form, c-if/c-for/c-class, data-field-type +- [docs/twig-filters.md](docs/twig-filters.md) — Twig filters reference (get, hook, module, queryDB, etc.) +- [docs/hooks-and-api.md](docs/hooks-and-api.md) — PHP hooks, CmsApi, CocoDB, database operations +- [docs/css-js-conventions.md](docs/css-js-conventions.md) — CSS/JS patterns, Tailwind, BEM, Vue 3 integration diff --git a/docs/builder-fields.md b/docs/builder-fields.md index 098a5f9..1ebb724 100644 --- a/docs/builder-fields.md +++ b/docs/builder-fields.md @@ -1,32 +1,213 @@ -# Builder Fields & Attributes +# Builder Fields & Acai Attributes ## Field Types (`data-field-type`) -The builder uses `data-field-type` attributes to define editable areas in module templates. +The builder uses `data-field-type` attributes on HTML elements to define editable areas. - +| Type | Description | Returns | +|------|-------------|---------| +| `textfield` | Single line text | String | +| `headfield` | Heading text (generates `_tag` variable for semantic tag: h1-h6) | String | +| `textbox` | Multi-line text | String | +| `wysiwyg` | Rich text editor (HTML output) | HTML string | +| `link` | URL field (already includes slashes) | String | +| `upload` | Single image/file | Array: `[0].urlPath`, `[0].info1` (alt), `[0].info2-4` | +| `uploadMulti` | Multiple images | Iterable: `item.urlPath` | +| `list` | Dropdown (fixed options or from table) | String or foreign key num | +| `multiv2` | Repeatable group of fields | Array of objects | + +### headfield Example + +```html +<{{ title_tag | default('h2') }} data-field-type="headfield" class="text-3xl font-bold"> + Section Title + +``` + +### upload Example + +```html + +{{ image[0].info1 }} + + +
+ {{ photo.info1 }} +
+``` + +### list Example + +```html + + + + + +``` -## Conditional Attributes +## Acai Attributes -### `c-if` +### `c-if` — Conditional Rendering - +```html + +
Banner content
-### `c-for` + +
Grid layout
- + +
{{ subtitle }}
+``` -### `c-class` +### `c-else` - +Must immediately follow the `c-if` element: + +```html +
+ +
+
+

No image available

+
+``` + +### `c-for` — Iteration + +```html + +
+

{{ item.title }}

+
+ + +
+

{{ product.nombre }}

+
+``` + +Available inside loop: `loop.index` (1-based), `loop.index is odd`, `loop.index is even` + +### `c-class` — Dynamic CSS Classes + +```html +
+ Content +
+``` + +### `c-hidden` — Hidden Elements + +Element is not rendered but can declare builder variables: + +```html +
+ +
+``` + +### `c-required` — Conditional Required Fields + +```html + +``` ## Forms (`c-form`) - +Complete form handling with automatic validation, storage, and email sending. + +```html +
+ + + + + + +``` + +### c-form Attributes + +| Attribute | Description | +|-----------|-------------| +| `tableName="'table'"` | Store submissions in database table | +| `mailRecord="['correos', 'ID']"` | Email template from `correos` table | +| `sendTo="'email@domain.com'"` | Recipient email(s), comma-separated | +| `sendToClient="'fieldname'"` | Field containing client's email for auto-reply | +| `captcha="true"` | Enable Google reCAPTCHA | +| `honeypot="true"` | Anti-spam hidden field | +| `messageOK="'text'"` | Success message | +| `messageKO="'text'"` | Error message | +| `redirect="'/path/'"` | Redirect after successful submit | +| `attachFiles="true"` | Attach uploaded files to email | +| `showImages="true"` | Show image thumbnails in email | -## Builder Variables Access +## Built-in Components - +### Carousel (`c-tns-wrapper`) + +```html +
+
+ +
+
+``` + +### Lightbox + +```html + + + +``` + +### Breadcrumb + +```html + +``` + +### Animate On Scroll (AOS) + +```html +
+ Animated content +
+``` + +### Lazy Loading + +```html + + + +``` diff --git a/docs/css-js-conventions.md b/docs/css-js-conventions.md new file mode 100644 index 0000000..f117c8a --- /dev/null +++ b/docs/css-js-conventions.md @@ -0,0 +1,101 @@ +# CSS & JavaScript Conventions + +## CSS + +### Tailwind First + +Use Tailwind CSS as the primary styling method. Only use custom CSS when Tailwind is insufficient. + +```html + +
+

Title

+
+ + +``` + +### BEM for Custom CSS + +When custom CSS is needed, scope everything under a root class using BEM naming: + +```css +/* Root class in kebab-case */ +.hero-section { } +.hero-section__title { } +.hero-section__image { } +.hero-section--dark { } +``` + +Never use global classes without a module prefix. + +### CSS Variables + +Acai provides theme variables: + +```css +var(--main-color) /* Primary brand color */ +var(--main-color-light) /* Lighter variant */ +var(--main-color-dark) /* Darker variant */ +``` + +### Utility Classes (Built-in) + +| Class | Description | +|-------|-------------| +| `transition3s` | 0.3s smooth transition | +| `click-a-child` | Makes parent clickable via child `` tag | +| `line-clamp2` / `line-clamp3` / `line-clamp5` | Text truncation with ellipsis | +| `filter-white` | CSS filter to make images/icons white | +| `lazyload` | Lazy loading (use with `data-src`) | + + +## JavaScript + +### Module Scripts (`script.js`) + +Keep JavaScript scoped to the module. Use `section_id` for targeting: + +```js +// Scope to this module instance +const section = document.getElementById('{{ section_id }}'); +if (section) { + const buttons = section.querySelectorAll('.btn'); + // ... +} +``` + +### CmsApi (Client-Side) + +```js +// Call a hook +CmsApi.hook('/hooks/module_id/', { action: 'getData', id: 123 }, function(response) { + console.log(response); +}); +``` + +### Vue 3 Integration + +For complex interactivity, use Vue 3 via CDN with Composition API: + +```html +
+

${ message }

+ +
+ + +``` + +**Important:** Use `'${'` and `'}'` as Vue delimiters to avoid conflicts with Twig's `{{ }}` syntax. diff --git a/docs/hooks-and-api.md b/docs/hooks-and-api.md index aaaa101..b9517a1 100644 --- a/docs/hooks-and-api.md +++ b/docs/hooks-and-api.md @@ -2,43 +2,141 @@ ## Hooks -Hooks are PHP files in the `hooks/` directory that execute server-side logic at specific lifecycle points. +Hooks are PHP files in the `hooks/` directory that execute server-side logic. They can also live inside a module at `template/estandar/modulos//hook.php`. -### Hook Types +### How to Call Hooks - +**From Twig:** +```twig +{{ 'hooks/module_id/' | hook({param1: 'value1', param2: variable}) }} +``` -### Hook File Naming +**From HTML (with result):** +```html + +

{{ myVar }}

+``` - +**From JavaScript:** +```js +CmsApi.hook('/hooks/module_id/', { param1: 'value1' }, function(response) { + console.log(response); +}); +``` -### Hook Examples +**From c-form:** +Hooks are automatically triggered on form submission when configured. - +### Hook Parameters + +Parameters are received as PHP variables: + +```php +` tag, the output is captured into the result variable. -## CmsApi +## CmsApi (PHP) -The `CmsApi` class provides methods to interact with the CMS from hooks. +Server-side API for database operations. Available in all hooks. - +### Read Records + +```php +// Get all records +$products = CmsApi::get('productos'); + +// With WHERE condition +$active = CmsApi::get('productos', ['active' => 1]); + +// With order and limit +$latest = CmsApi::get('noticias', [], 'fecha DESC', 5); + +// With operators +$expensive = CmsApi::get('productos', ['precio' => ['>=' => 100]]); +$search = CmsApi::get('productos', ['nombre' => ['LIKE' => '%keyword%']]); +$inList = CmsApi::get('productos', ['categoria_num' => ['IN' => [1, 2, 3]]]); +``` + +### Insert Records + +```php +$newRecord = CmsApi::insert('contacto', [ + 'nombre' => 'John', + 'email' => 'john@example.com', + 'mensaje' => 'Hello', +]); +``` + +### Update Records + +```php +CmsApi::update('productos', + ['precio' => 29.99, 'activo' => 1], // fields to update + ['num' => 42] // where condition +); +``` + +### Delete Records + +```php +CmsApi::delete('productos', ['num' => 42]); +``` + +### Important Rules + +- Table names **without** `cms_` prefix +- Primary key is always `num`, never `id` +- Upload fields are handled separately (not via insert/update) +- Operators: `=`, `!=`, `>`, `>=`, `<`, `<=`, `LIKE`, `IN` + + +## CmsApi (JavaScript — Client-Side) + +```js +// Hook call +CmsApi.hook('/hooks/module_id/', { param: 'value' }, function(response) { + // response is the hook output +}); + +// Record operations (if exposed via hooks) +CmsApi.get('tableName', { where: conditions }, function(records) { + // records array +}); +``` ## CocoDB -`CocoDB` is the database abstraction layer for Acai. +Low-level database abstraction layer. Use when CmsApi is too high-level. - + -## Database Operations +## Table Schemas -### Direct Queries +Table schemas are stored as JSON in `cms/data/schema/`. Each file defines: +- Field names and types +- Validation rules +- Relationships (foreign keys) +- Display configuration - +### Field Format Types -### Table Schemas - -Table schemas are stored as JSON files in `cms/data/schema/`. Each file defines the fields, types, and configuration of a CMS table. - - +| Type | PHP Format | Notes | +|------|-----------|-------| +| Text | String | Plain text | +| Date/time | `YYYY-MM-DD HH:mm:ss` | MySQL datetime format | +| Checkbox | `1` or `0` | Boolean as integer | +| WYSIWYG | HTML string | Rich text with Tailwind classes | +| List | String or num | Foreign key if linked to table | +| Multivalores | JSON string | Serialized array | +| Upload | — | Handled separately, never in insert/update | diff --git a/docs/modular-system.md b/docs/modular-system.md index 5be4ec3..ecec0c3 100644 --- a/docs/modular-system.md +++ b/docs/modular-system.md @@ -4,51 +4,102 @@ Modules are the visual building blocks of Acai websites. Each module lives in `template/estandar/modulos//`. -### File structure +### File Structure ``` / -├── index-base.tpl # Source Twig template (EDIT THIS) +├── index-base.tpl # Source template (EDIT THIS) ├── index.tpl # Compiled output (auto-generated, do NOT edit) +├── index-twig.tpl # Compiled Twig output (auto-generated, do NOT edit) +├── builder.json # Compiled builder vars (auto-generated, do NOT edit) ├── style.css # Module-scoped styles └── script.js # Module JavaScript ``` -### Twig Templates +### Template Syntax - +Templates use a hybrid of **Twig** and **Acai attributes**. The source file is always `index-base.tpl`. -### Custom Twig Filters +```html +
+ +
+``` - +### Including Modules from Other Modules -### Builder Variables +```html + +``` - +Parameters are received as variables inside the included module. -### Calling Modules from Other Modules +### Global Variables - +| Variable | Description | +|----------|-------------| +| `section_id` | Unique ID per module instance (use for anchors, JS scoping) | +| `interno` | `true` when viewing in CMS editor, `false` on public site | +| `server.HTTP_HOST` | Current domain | +| `loop.index` | 1-based iteration index (inside `c-for`) | +| `loop.index is odd` / `loop.index is even` | For alternating layouts | ## General Sections -General sections are reusable layout blocks (headers, footers, sidebars) shared across pages. +General sections are database-backed templates used for record views, headers, footers, and reusable layouts. They use the same template engine as modules. - +### Key Differences from Modules + +- Access record data via the `thisrecord` variable +- Upload fields return **arrays**: `thisrecord.image[0].urlPath` +- Additional upload metadata: `info1` (alt text), `info2`, `info3`, `info4` +- Foreign key fields use `_num` suffix: `thisrecord.category_num` +- Saved via `save_general_section()` (not `save_module()`) +- Parser type 2 = Twig (recommended), 0 = Acai legacy syntax + +### Example: Record Template + +```html +
+ {{ thisrecord.imagen[0].info1 }} +

{{ thisrecord.nombre }}

+

{{ thisrecord.descripcion | raw }}

+ {{ thisrecord.precio }}€ +
+``` + +### Variable Assignment + +Use `` tag to create variables from queries: + +```html + + +``` -## CSS & JavaScript +## Repeatable Content (multiv2) -### Module Styles (`style.css`) +The `multiv2` builder field type creates repeatable groups of fields: - +```html +
+

{{ item.title }}

+

{{ item.description }}

+ +
+``` -### Module Scripts (`script.js`) - - - - -## Builder Field Types - - +Access individual items: `record.items[0].title`, `record.items[1].image`, etc. diff --git a/docs/twig-filters.md b/docs/twig-filters.md new file mode 100644 index 0000000..54b2ce8 --- /dev/null +++ b/docs/twig-filters.md @@ -0,0 +1,118 @@ +# Twig Filters Reference + +Acai uses Twig **filters** (with `|` pipe syntax). Never use Twig functions — only filters are supported. + +## Database Queries + +### `get` — Query Table + +```twig +{# All records #} +{% set products = 'productos' | get() %} + +{# With WHERE #} +{% set active = 'productos' | get({activo: 1}) %} + +{# With WHERE + ORDER + LIMIT #} +{% set latest = 'noticias' | get({publicado: 1}, 'fecha DESC', 6) %} + +{# Single record (first result) #} +{% set product = 'productos' | get({num: 42}) %} +{{ product[0].nombre }} +``` + +### `queryDB` — Raw SQL Query + +```twig +{% set results = 'SELECT * FROM cms_productos WHERE precio > 100 ORDER BY precio ASC' | queryDB() %} +``` + +Note: Raw SQL uses the full table name WITH `cms_` prefix. + +## Module & Hook Execution + +### `hook` — Execute PHP Hook + +```twig +{# Call hook and output result #} +{{ 'hooks/module_id/' | hook({param1: 'value', param2: variable}) }} + +{# Capture into variable #} +{% set result = 'hooks/module_id/' | hook({action: 'getData'}) %} +``` + +### `module` — Render Another Module + +```twig +{{ 'other_module_id' | module({param1: value1}) }} +``` + +## Text & Content + +### `translate` — Translation + +```twig +{{ 'Hello' | translate }} +{{ variable | translate }} +``` + +### `raw` — Render HTML Without Escaping + +```twig +{{ record.description | raw }} +``` + +### `truncate` — Text Truncation + +```twig +{{ record.description | truncate(150) }} +``` + +### `json_decode` — Parse JSON String + +```twig +{% set data = jsonString | json_decode %} +{{ data.key }} +``` + +## Images + +### `imagec` — Image Optimization/Resize + +```twig +{# Resize to width #} + + +{# In srcset #} + +``` + +## Operators & Syntax + +### Concatenation + +Twig uses `~` for string concatenation (not `.` or `+`): + +```twig +{{ 'Hello ' ~ name ~ '!' }} +{% set url = '/products/' ~ product.slug ~ '/' %} +``` + +### Ternary / Default + +```twig +{{ title | default('Default Title') }} +{{ isActive ? 'active' : 'inactive' }} +``` + +### Comparisons in Twig + +```twig +{% if items | length > 0 %} +{% if type == 'premium' %} +{% if name is not empty %} +``` + +Note: In `c-if` attributes, use `=` (single equals) for equality. In Twig `{% if %}` blocks, use `==` (double equals).