# Media and inline editing

Configure rich content and manage files and image presentation.

## Rich content fields: end-to-end

This section shows the full flow for an editable HTML field: resource definition → template output → inline admin editing.

### Step 1: Define the field in `.meta`

```json
"intro": {
  "name": "Intro",
  "type": "html",
  "buttons": "bold,italic",
  "admin_col": false,
  "i18n": true
},
"body": {
  "name": "Body",
  "type": "html",
  "buttons": "h2,h3,h4,bold,italic,orderedlist,unorderedlist,quote,anchor",
  "media": true,
  "media_sizes": "sm-90,md-70,lg-60,xl-50,xxl-40",
  "admin_col": false,
  "i18n": true
}
```

- `buttons` is mandatory - always specify the exact toolbar buttons needed
- `media: true` enables the media browser; requires `media_sizes`
- `admin_col: false` is mandatory for html fields (too large for table view)
- Add `i18n: true` if the site is multi-language

### Step 2: Load the record in route.inc

```php
<?php
router_deny();
$parts = router_match(__FILE__);
if ($parts === false) return;
load_library('data');
$record = data_read('articles', $parts[0]);
if (!$record) return;
set_variable('article', $record);
router_accept();
```

### Step 3: Output in the template

Use `[#get-html#]` to output an editable html field. When a logged-in admin views the page, the field becomes inline-editable directly in the frontend.

```html
<div class="prose">
  [#get-html article.intro#]
</div>

<div class="prose prose-lg">
  [#get-html article.body#]
</div>
```

For multi-language content, `[#get-html#]` resolves i18n field objects automatically. Add a language suffix when you explicitly need one language:

```
[#get-html article.body#]
[#get-html article.body.nl#]
```

### Step 4: Enable inline editing

Add `[#html#]` to the route and ensure the user module is active (auto-loaded). Logged-in editors (`edit-inline-content`) then get the page actions pill: **Edit** switches inline editing on and off (asking to save or discard unsaved changes), and the settings button opens page settings or the record's admin page (`page-settings-link`). The pill can be dragged anywhere and docks in a corner when dropped near one. While editing, the focused field shows its field bar with Save.

### Inline editing attributes

For finer control over the inline editor (e.g. different toolbar than the `.meta` default, or to enable media on a field that doesn't have it in `.meta`), add `data-nb-edit` directly to a wrapper element:

```html
<div data-nb-edit="event.[#record.uuid#].main_text" data-nb-edit-options='{
    "buttons":"h2,h3,bold,italic,anchor",
    "media": true,
    "media_sizes":"md-70,lg-60"}'>
  [#get-html record.main_text#]
</div>
```

For inline image replacement, use `data-nb-edit-img` on the image wrapper:

```html
<div data-nb-edit-img="event.[#record.uuid#].main_img">
  [#get-img-html [#record.main_img#] sizes="xs-100,md-50"#]
</div>
```

Both attributes only activate for logged-in admins. The value format is `resource.uuid.field`.

---

## File upload

Files are uploaded to the special `.files` resource as `multipart/form-data`:

```bash
curl -X POST "/api/v1/.files" \
  -H "Authorization: Bearer TOKEN" \
  -F "file=@photo.jpg"
```

Response includes the file UUID, which is the **MD5 checksum** of the file content:

```json
{
  "files": {
    "uuid": "44556fd2a0d9463b6506e6e101e20bfe",
    "name": "photo.jpg",
    "type": "image/jpeg",
    "size": 2048
  },
  "message": "RESOURCE_CREATED",
  "code": 201
}
```

Re-uploading an identical file returns `RESOURCE_CREATED` with the same UUID - the upload is idempotent by checksum.

## File metadata: `.files_meta`

Every uploaded file has a corresponding metadata record in `.files_meta/{uuid}`. This is a separate resource from `.files` (which stores the raw binary). The metadata record contains:

| Field | Description |
|---|---|
| `uuid` | File UUID - MD5 checksum of the file content |
| `name` | Original filename (e.g. `KH-Menukaart.pdf`) |
| `title` | Admin-set display title (e.g. `KetelHuis Menukaart`) - may be empty |
| `type` | MIME type (e.g. `application/pdf`, `image/jpeg`) |
| `size` | File size in bytes |

When displaying a file reference stored as a UUID (e.g. in page settings or a resource field), always read the display name and type from `.files_meta`, not from the UUID itself. In templates, `nb.media_library.unfiltered` contains the full `.files_meta` records loaded at page init - use this for client-side display without extra API calls.

Prefer `title` over `name` as the user-facing display label; fall back to `name` when `title` is empty.

## Image serving

Uploaded images are served at `/img/{uuid}/{spec}`:

| Spec | Example | Description |
|---|---|---|
| `{W}x{H}f` | `300x300f` | Fit into box, aspect ratio preserved |
| `{W}x{H}c` | `300x200c` | Crop from center to exact dimensions |
| `{W}w` | `300w` | Resize to width |
| `{H}h` | `300h` | Resize to height |

```
/img/44556fd2a0d9463b6506e6e101e20bfe/500x500c
```

Images are served as **WebP**. Upscaling is not allowed.
