odac 1.4.3 → 1.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/rules/coding.md +2 -2
- package/.agent/rules/memory.md +5 -1
- package/.github/workflows/release.yml +2 -0
- package/.husky/pre-push +0 -1
- package/.kiro/steering/coding.md +27 -0
- package/.kiro/steering/memory.md +56 -0
- package/.kiro/steering/project.md +30 -0
- package/.kiro/steering/workflow.md +16 -0
- package/CHANGELOG.md +98 -0
- package/README.md +2 -1
- package/client/odac.js +121 -2
- package/docs/ai/skills/backend/authentication.md +7 -5
- package/docs/ai/skills/backend/controllers.md +24 -3
- package/docs/ai/skills/backend/forms.md +8 -6
- package/docs/ai/skills/backend/image-processing.md +93 -0
- package/docs/ai/skills/backend/request_response.md +2 -2
- package/docs/ai/skills/backend/routing.md +11 -0
- package/docs/ai/skills/backend/structure.md +1 -1
- package/docs/ai/skills/backend/views.md +34 -9
- package/docs/ai/skills/frontend/navigation.md +45 -1
- package/docs/ai/skills/frontend/realtime.md +18 -2
- package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +24 -0
- package/docs/backend/07-views/03-template-syntax.md +65 -15
- package/docs/backend/07-views/03-variables.md +22 -7
- package/docs/backend/07-views/11-image-optimization.md +197 -0
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +22 -0
- package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +51 -0
- package/package.json +5 -2
- package/src/Auth.js +8 -4
- package/src/Config.js +5 -0
- package/src/Database/ConnectionFactory.js +16 -0
- package/src/Ipc.js +3 -2
- package/src/Lang.js +17 -10
- package/src/Odac.js +1 -0
- package/src/Request.js +20 -20
- package/src/Route.js +39 -3
- package/src/Validator.js +5 -5
- package/src/View/Image.js +495 -0
- package/src/View.js +4 -0
- package/template/controller/page/about.js +3 -3
- package/template/controller/page/index.js +2 -2
- package/template/public/assets/js/app.js +38 -54
- package/template/skeleton/main.html +4 -4
- package/template/view/content/about.html +64 -60
- package/template/view/content/home.html +148 -175
- package/template/view/css/app.css +46 -0
- package/template/view/footer/main.html +10 -9
- package/template/view/header/main.html +34 -11
- package/test/Auth/verifyMagicLink.test.js +281 -0
- package/test/Client/load.test.js +306 -0
- package/test/Lang/get.test.js +37 -11
- package/test/Odac/image.test.js +61 -0
- package/test/Route/set.test.js +102 -0
- package/test/View/Image/buildFilename.test.js +62 -0
- package/test/View/Image/hash.test.js +59 -0
- package/test/View/Image/isAvailable.test.js +15 -0
- package/test/View/Image/parse.test.js +83 -0
- package/test/View/Image/process.test.js +38 -0
- package/test/View/Image/render.test.js +117 -0
- package/test/View/Image/serve.test.js +56 -0
- package/test/View/Image/url.test.js +53 -0
- package/test/View/constructor.test.js +10 -0
- package/template/public/assets/css/style.css +0 -1835
|
@@ -18,7 +18,7 @@ Handling incoming data and sending structured responses.
|
|
|
18
18
|
3. **Returning Data**:
|
|
19
19
|
- `return { ... }`: Returns JSON.
|
|
20
20
|
- `return Odac.return({ ... })`: Explicit JSON return.
|
|
21
|
-
- `Odac.
|
|
21
|
+
- `Odac.Request.header('Key', 'Value')`: Set custom headers.
|
|
22
22
|
|
|
23
23
|
## Reference Patterns
|
|
24
24
|
### 1. Unified Request Handling
|
|
@@ -36,7 +36,7 @@ module.exports = async function(Odac) {
|
|
|
36
36
|
### 2. Header and Status Management
|
|
37
37
|
```javascript
|
|
38
38
|
module.exports = function(Odac) {
|
|
39
|
-
Odac.
|
|
39
|
+
Odac.Request.header('Content-Type', 'text/plain');
|
|
40
40
|
return "Raw text response";
|
|
41
41
|
};
|
|
42
42
|
```
|
|
@@ -17,6 +17,8 @@ Routes are defined in the `route/` directory. ODAC uses a two-phase routing stra
|
|
|
17
17
|
- `Odac.Route.page(url, controller)`: For HTML views (GET).
|
|
18
18
|
- `Odac.Route.get(url, controller)`: Targeted GET requests.
|
|
19
19
|
- `Odac.Route.post(url, controller)`: Sensitive POST requests (CSRF enabled by default).
|
|
20
|
+
- `Odac.Route.ws(url, controller)`: Bidirectional and persistent WebSocket connections.
|
|
21
|
+
- `Odac.Route.authWs(url, controller)`: Authenticated WebSocket connections.
|
|
20
22
|
2. **Parameters**: Use `{id}` syntax for dynamic segments. Accessed via `Odac.request('id')`.
|
|
21
23
|
3. **Middlewares**: Chain logic using `.use('name')`. Global middlewares reside in `middleware/`.
|
|
22
24
|
4. **Error Handling**: Use `Odac.Route.error(code, controller)` for custom 404/500 pages.
|
|
@@ -51,6 +53,15 @@ Odac.Route.error(500, 'errors/ServerError');
|
|
|
51
53
|
Odac.Route.post('/api/webhook', 'Api@webhook', { token: false });
|
|
52
54
|
```
|
|
53
55
|
|
|
56
|
+
### 5. Realtime & WebSocket Routes
|
|
57
|
+
```javascript
|
|
58
|
+
// Public WebSocket
|
|
59
|
+
Odac.Route.ws('/chat', 'Chat');
|
|
60
|
+
|
|
61
|
+
// Authenticated WebSocket with path params
|
|
62
|
+
Odac.Route.authWs('/game/{roomId}', 'Game@join');
|
|
63
|
+
```
|
|
64
|
+
|
|
54
65
|
## Best Practices
|
|
55
66
|
- **Method Specification**: Use `.page()` for views to enable AJAX navigation compatibility.
|
|
56
67
|
- **Static First**: Prefer exact URL matches over parametric ones where possible (faster).
|
|
@@ -50,7 +50,7 @@ module.exports = User;
|
|
|
50
50
|
class User {
|
|
51
51
|
async show(Odac) {
|
|
52
52
|
// Service is automatically available as Odac.User
|
|
53
|
-
const profile = await Odac.User.getProfile(Odac.
|
|
53
|
+
const profile = await Odac.User.getProfile(await Odac.request('id'));
|
|
54
54
|
|
|
55
55
|
return Odac.View.make('user.profile', { profile });
|
|
56
56
|
}
|
|
@@ -14,12 +14,18 @@ Views in ODAC are logic-light but powerful. They support automatic XSS protectio
|
|
|
14
14
|
|
|
15
15
|
## Core Rules
|
|
16
16
|
1. **Skeleton Architecture**: Use `Odac.View.skeleton('name')` to wrap content in a layout.
|
|
17
|
-
2. **Data Binding**:
|
|
18
|
-
-
|
|
19
|
-
- `{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
2. **Data Binding — Two Equivalent Syntaxes**:
|
|
18
|
+
- `<odac var="key" />`: Tag-based output (HTML-escaped, XSS-safe).
|
|
19
|
+
- `{{ key }}`: Inline/interpolation output (HTML-escaped, XSS-safe). Identical behavior to `<odac var>`.
|
|
20
|
+
- `<odac var="key" raw />` or `{!! key !!}`: Raw output (Use with extreme caution).
|
|
21
|
+
3. **Choosing the Right Syntax**:
|
|
22
|
+
- **Inside HTML attributes** (`src`, `alt`, `href`, `class`, `value`, etc.) → Always prefer `{{ }}`. It reads naturally and keeps markup clean.
|
|
23
|
+
- **Inline within text or mixed HTML** → Prefer `{{ }}` for short interpolations.
|
|
24
|
+
- **Standalone block output** (the variable is the only content of an element) → Prefer `<odac var="" />` for structural clarity and IDE support.
|
|
25
|
+
- Both syntaxes compile to the same engine output. The choice is about readability, not functionality.
|
|
26
|
+
4. **Conditionals**: Use `<odac:if condition="VAR"> ... </odac:if>`.
|
|
27
|
+
5. **Looping**: Use `<odac:for in="ARRAY" value="ITEM"> ... </odac:for>` or the performance-optimized `[[odac_for ...]]`.
|
|
28
|
+
6. **Server-Side JS**: Use `<script:odac>` for complex calculations during rendering.
|
|
23
29
|
|
|
24
30
|
## Reference Patterns
|
|
25
31
|
|
|
@@ -35,8 +41,17 @@ Odac.View.set({
|
|
|
35
41
|
|
|
36
42
|
### 2. Template Syntax Reference
|
|
37
43
|
```html
|
|
38
|
-
<!--
|
|
39
|
-
<h1
|
|
44
|
+
<!-- Standalone block output — prefer <odac var> -->
|
|
45
|
+
<h1><odac var="title" /></h1>
|
|
46
|
+
|
|
47
|
+
<!-- Inside attributes — prefer {{ }} -->
|
|
48
|
+
<img src="{{ product.image }}" alt="{{ product.name }}">
|
|
49
|
+
<a href="/user/{{ user.id }}" class="btn {{ isActive ? 'active' : '' }}">Profile</a>
|
|
50
|
+
<input type="text" value="{{ query }}">
|
|
51
|
+
|
|
52
|
+
<!-- Inline text interpolation — prefer {{ }} -->
|
|
53
|
+
<p>Welcome, {{ user.name }}. You have {{ notifications }} new messages.</p>
|
|
54
|
+
<span>${{ product.price }}</span>
|
|
40
55
|
|
|
41
56
|
<!-- Conditional -->
|
|
42
57
|
<odac:if condition="stats.users > 100">
|
|
@@ -62,7 +77,17 @@ Perfect for calculations that shouldn't clutter the controller but are too compl
|
|
|
62
77
|
<p>Tax: ${{ tax }}</p>
|
|
63
78
|
```
|
|
64
79
|
|
|
80
|
+
## Syntax Selection Guide
|
|
81
|
+
|
|
82
|
+
| Context | Preferred Syntax | Example |
|
|
83
|
+
|---------|-----------------|---------|
|
|
84
|
+
| HTML attributes (`src`, `href`, `alt`, `class`, `value`) | `{{ }}` | `<img src="{{ photo.url }}" alt="{{ photo.caption }}">` |
|
|
85
|
+
| Inline text within elements | `{{ }}` | `<p>Hello, {{ user.name }}</p>` |
|
|
86
|
+
| Standalone element content | `<odac var />` | `<h1><odac var="title" /></h1>` |
|
|
87
|
+
| Raw HTML output (trusted only) | `<odac var raw />` or `{!! !!}` | `<div><odac var="content" raw /></div>` |
|
|
88
|
+
|
|
65
89
|
## Security Best Practices
|
|
66
|
-
- **
|
|
90
|
+
- **Both `{{ }}` and `<odac var>` are XSS-safe**: Both apply HTML escaping by default. Use either with confidence.
|
|
91
|
+
- **Raw output requires trust**: Only use `raw` / `{!! !!}` with content you fully control. Never with user input.
|
|
67
92
|
- **Limit `<script:odac>`**: Do not perform database queries or API calls inside views; keep them in the controller.
|
|
68
93
|
- **Partial Awareness**: Use `<odac:include view="path.to.view" />` for reusable components.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: frontend-navigation-spa-skill
|
|
3
3
|
description: Single-page navigation patterns in odac.js for smooth transitions, route control, and lifecycle-safe execution.
|
|
4
4
|
metadata:
|
|
5
|
-
tags: frontend, navigation, spa, ajax-navigation, page-lifecycle, transitions
|
|
5
|
+
tags: frontend, navigation, spa, ajax-navigation, page-lifecycle, transitions, view-transitions
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Frontend Navigation & SPA Skill
|
|
@@ -13,6 +13,7 @@ Smooth transitions and single-page application behavior using `odac.js`.
|
|
|
13
13
|
1. **Selection**: Enable via `Odac.action({ navigate: 'main' })`.
|
|
14
14
|
2. **Exclusion**: Use `data-navigate="false"` or `.no-navigate` class for full reloads.
|
|
15
15
|
3. **Lifecycle**: Use `load` and `page` events to run code after navigation.
|
|
16
|
+
4. **View Transitions**: Add `odac-transition` attribute to elements for native browser View Transition API animations. Zero-config — no JS setup required.
|
|
16
17
|
|
|
17
18
|
## Patterns
|
|
18
19
|
```javascript
|
|
@@ -25,3 +26,46 @@ Odac.action({
|
|
|
25
26
|
}
|
|
26
27
|
});
|
|
27
28
|
```
|
|
29
|
+
|
|
30
|
+
## View Transitions (Native Browser API)
|
|
31
|
+
|
|
32
|
+
Elements with the `odac-transition` attribute automatically use the browser's View Transition API instead of the legacy fade animation. The attribute value becomes the `view-transition-name`, enabling per-element morphing between pages.
|
|
33
|
+
|
|
34
|
+
### HTML Usage
|
|
35
|
+
```html
|
|
36
|
+
<header odac-transition="header">Site Header</header>
|
|
37
|
+
<nav odac-transition="sidebar">Navigation</nav>
|
|
38
|
+
<main>Content updated by AJAX loader</main>
|
|
39
|
+
<img odac-transition="hero" src="/hero.jpg" />
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Behavior
|
|
43
|
+
- When `odac-transition` elements exist and the browser supports View Transition API → native transition is used.
|
|
44
|
+
- When no `odac-transition` elements exist or the API is unsupported → legacy fade fallback runs automatically.
|
|
45
|
+
- Transition names are applied before the snapshot and cleaned up after the transition completes.
|
|
46
|
+
- The attribute value must be unique per page (browser requirement for `view-transition-name`).
|
|
47
|
+
|
|
48
|
+
### CSS Customization
|
|
49
|
+
```css
|
|
50
|
+
/* Target a specific element's transition */
|
|
51
|
+
::view-transition-old(hero) {
|
|
52
|
+
animation: fade-out 0.3s ease;
|
|
53
|
+
}
|
|
54
|
+
::view-transition-new(hero) {
|
|
55
|
+
animation: fade-in 0.3s ease;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Slide sidebar from left */
|
|
59
|
+
::view-transition-old(sidebar) {
|
|
60
|
+
animation: slide-out-left 0.25s ease;
|
|
61
|
+
}
|
|
62
|
+
::view-transition-new(sidebar) {
|
|
63
|
+
animation: slide-in-left 0.25s ease;
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Rules
|
|
68
|
+
1. Each `odac-transition` value must be unique within the page.
|
|
69
|
+
2. Elements persist across navigations (e.g., shared header) for smooth morphing.
|
|
70
|
+
3. No JavaScript configuration needed — attribute-only setup.
|
|
71
|
+
4. Falls back to fade animation gracefully on unsupported browsers.
|
|
@@ -50,5 +50,21 @@ source.onmessage = (event) => {
|
|
|
50
50
|
|
|
51
51
|
## Best Practices
|
|
52
52
|
- **Resource Management**: Shared WebSocket automatically closes when the last tab using it is closed.
|
|
53
|
-
- **
|
|
54
|
-
- **
|
|
53
|
+
- **Security**: Always use `Odac.ws(url, { token: true })` for authenticated paths; the client will automatically attach the latest CSRF/Session token.
|
|
54
|
+
- **Distributed State**: Use `Odac.Ipc` in backend WS handlers to broadcast messages across multiple server workers.
|
|
55
|
+
|
|
56
|
+
### 4. Rooms & Broadcasting (Backend Example)
|
|
57
|
+
```javascript
|
|
58
|
+
// controller/ws/Game.js
|
|
59
|
+
module.exports = async function(Odac) {
|
|
60
|
+
const ws = Odac.ws;
|
|
61
|
+
const roomId = await Odac.request('roomId');
|
|
62
|
+
|
|
63
|
+
ws.join(roomId);
|
|
64
|
+
|
|
65
|
+
ws.on('message', (data) => {
|
|
66
|
+
// Broadcast only to this room
|
|
67
|
+
ws.to(roomId).send({ user: ws.id, move: data.move });
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
```
|
|
@@ -21,6 +21,7 @@ Remember the `Odac` object? It's your best friend inside a controller. It's pass
|
|
|
21
21
|
* `Odac.setInterval(callback, delay)`: Schedule repeating tasks (auto-cleanup).
|
|
22
22
|
* `Odac.setTimeout(callback, delay)`: Schedule one-time tasks (auto-cleanup).
|
|
23
23
|
* `Odac.stream(input)`: Create streaming responses (SSE).
|
|
24
|
+
* `Odac.image(src, options)`: Get a processed image URL (resize, format conversion, caching).
|
|
24
25
|
|
|
25
26
|
#### Memory-Safe Timers
|
|
26
27
|
|
|
@@ -41,3 +42,26 @@ module.exports = async (Odac) => {
|
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
With controllers and the `Odac` object, you have everything you need to start building powerful application logic!
|
|
45
|
+
|
|
46
|
+
#### Image Processing
|
|
47
|
+
|
|
48
|
+
Use `Odac.image()` when you need a processed image URL outside of templates — for JSON APIs, div backgrounds, email templates, or cron jobs:
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
module.exports = async (Odac) => {
|
|
52
|
+
// Get a resized WebP URL for an API response
|
|
53
|
+
const heroUrl = await Odac.image('/images/hero.jpg', { width: 1200 })
|
|
54
|
+
// → "/_odac/img/hero-1200-a1b2c3d4.webp"
|
|
55
|
+
|
|
56
|
+
// Pass a processed URL to the template for a CSS background
|
|
57
|
+
const bgUrl = await Odac.image('/images/banner.jpg', { width: 1920, quality: 90 })
|
|
58
|
+
Odac.set('bgUrl', bgUrl)
|
|
59
|
+
|
|
60
|
+
// Convert to a specific format
|
|
61
|
+
const pngLogo = await Odac.image('/images/logo.png', { width: 200, format: 'png' })
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Options:** `{ width, height, format, quality }` — same as `<odac:img>` tag attributes.
|
|
66
|
+
|
|
67
|
+
> When `sharp` is not installed, `Odac.image()` returns the original source path as a graceful fallback. For template usage with automatic `<img>` tag generation, see the [`<odac:img>` documentation](../07-views/11-image-optimization.md).
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
## 🔧 Template Syntax Overview
|
|
2
2
|
|
|
3
|
-
Odac uses a powerful template engine to create dynamic content in view files. The engine provides
|
|
3
|
+
Odac uses a powerful template engine to create dynamic content in view files. The engine provides two equivalent syntaxes for displaying variables, plus dedicated tags for conditionals, loops, translations, and more.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
### Two Syntaxes, One Engine
|
|
6
|
+
|
|
7
|
+
ODAC offers two ways to output variables. Both are HTML-escaped (XSS-safe) and compile to the same engine code. The choice is about readability, not functionality.
|
|
8
|
+
|
|
9
|
+
| Syntax | Best For | Example |
|
|
10
|
+
|--------|----------|---------|
|
|
11
|
+
| `<odac var="x" />` | Standalone block output where the variable is the main content | `<h1><odac var="title" /></h1>` |
|
|
12
|
+
| `{{ x }}` | Attributes, inline text, and mixed HTML where tag syntax would be verbose | `<img src="{{ photo.url }}" alt="{{ photo.caption }}">` |
|
|
13
|
+
|
|
14
|
+
> **Guideline:** Use `{{ }}` inside HTML attributes (`src`, `href`, `alt`, `class`, `value`, etc.) and for inline text interpolation. Use `<odac var>` for standalone element content. Both are equally supported and recommended.
|
|
6
15
|
|
|
7
16
|
### Quick Reference
|
|
8
17
|
|
|
@@ -13,10 +22,17 @@ This page provides a quick overview of all available template features. For deta
|
|
|
13
22
|
Display data passed from controllers using `Odac.set()`:
|
|
14
23
|
|
|
15
24
|
```html
|
|
16
|
-
<!--
|
|
17
|
-
<odac var="username"
|
|
25
|
+
<!-- Standalone block output — prefer <odac var> -->
|
|
26
|
+
<h1><odac var="username" /></h1>
|
|
27
|
+
|
|
28
|
+
<!-- Inside attributes — prefer {{ }} -->
|
|
29
|
+
<img src="{{ product.image }}" alt="{{ product.name }}">
|
|
30
|
+
<a href="/user/{{ user.id }}">Profile</a>
|
|
18
31
|
|
|
19
|
-
<!--
|
|
32
|
+
<!-- Inline text — prefer {{ }} -->
|
|
33
|
+
<p>Welcome, {{ user.name }}. You have {{ count }} items.</p>
|
|
34
|
+
|
|
35
|
+
<!-- Raw HTML output (trusted content only) -->
|
|
20
36
|
<odac var="htmlContent" raw />
|
|
21
37
|
|
|
22
38
|
<!-- String literals -->
|
|
@@ -130,6 +146,23 @@ Execute JavaScript on the server during template rendering:
|
|
|
130
146
|
|
|
131
147
|
**[→ Learn more about Backend JavaScript](./08-backend-javascript.md)**
|
|
132
148
|
|
|
149
|
+
### Image Optimization
|
|
150
|
+
|
|
151
|
+
Automatically resize and convert images to modern formats:
|
|
152
|
+
|
|
153
|
+
```html
|
|
154
|
+
<!-- Auto-convert to WebP (default) -->
|
|
155
|
+
<odac:img src="/images/hero.jpg" />
|
|
156
|
+
|
|
157
|
+
<!-- Resize + convert -->
|
|
158
|
+
<odac:img src="/images/photo.jpg" width="800" height="600" format="webp" />
|
|
159
|
+
|
|
160
|
+
<!-- With standard HTML attributes -->
|
|
161
|
+
<odac:img src="/images/avatar.jpg" width="64" height="64" alt="Avatar" loading="lazy" />
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**[→ Learn more about Image Optimization](./11-image-optimization.md)**
|
|
165
|
+
|
|
133
166
|
### Accessing the Odac Object
|
|
134
167
|
|
|
135
168
|
Full access to the Odac object in templates:
|
|
@@ -146,8 +179,10 @@ Full access to the Odac object in templates:
|
|
|
146
179
|
|
|
147
180
|
| Feature | Syntax | Documentation |
|
|
148
181
|
|---------|--------|---------------|
|
|
149
|
-
| Variable (
|
|
150
|
-
|
|
|
182
|
+
| Variable (standalone) | `<odac var="x" />` | [Variables](./03-variables.md) |
|
|
183
|
+
| Variable (inline/attribute) | `{{ x }}` | [Variables](./03-variables.md) |
|
|
184
|
+
| Raw HTML (tag) | `<odac var="x" raw />` | [Variables](./03-variables.md) |
|
|
185
|
+
| Raw HTML (inline) | `{!! x !!}` | [Variables](./03-variables.md) |
|
|
151
186
|
| String | `<odac>text</odac>` | [Variables](./03-variables.md) |
|
|
152
187
|
| Query Parameter | `<odac get="key" />` | [Request Data](./04-request-data.md) |
|
|
153
188
|
| Translation | `<odac translate>key</odac>` | [Translations](./07-translations.md) |
|
|
@@ -161,21 +196,36 @@ Full access to the Odac object in templates:
|
|
|
161
196
|
| Continue | `<odac:continue />` | [Loops](./06-loops.md) |
|
|
162
197
|
| JavaScript | `<script:odac>...</script:odac>` | [Backend JavaScript](./08-backend-javascript.md) |
|
|
163
198
|
| Comment | `<!--odac ... odac-->` | [Comments](./09-comments.md) |
|
|
199
|
+
| Image | `<odac:img src="..." />` | [Image Optimization](./11-image-optimization.md) |
|
|
164
200
|
|
|
165
|
-
###
|
|
201
|
+
### Syntax Selection Guide
|
|
166
202
|
|
|
167
|
-
|
|
203
|
+
Choose the right syntax based on context for clean, readable templates:
|
|
168
204
|
|
|
169
205
|
```html
|
|
170
|
-
<!--
|
|
171
|
-
{{
|
|
206
|
+
<!-- ✅ Attributes — use {{ }} -->
|
|
207
|
+
<img src="{{ product.image }}" alt="{{ product.name }}">
|
|
208
|
+
<a href="/products/{{ product.id }}" class="card {{ isActive ? 'active' : '' }}">
|
|
209
|
+
<input type="text" name="search" value="{{ query }}">
|
|
210
|
+
|
|
211
|
+
<!-- ✅ Inline text — use {{ }} -->
|
|
212
|
+
<p>Hello, {{ user.name }}. You have {{ count }} notifications.</p>
|
|
213
|
+
<span>${{ product.price }}</span>
|
|
214
|
+
|
|
215
|
+
<!-- ✅ Standalone block content — use <odac var> -->
|
|
216
|
+
<h1><odac var="pageTitle" /></h1>
|
|
217
|
+
<td><odac var="user.email" /></td>
|
|
218
|
+
|
|
219
|
+
<!-- ✅ Raw output — either form works -->
|
|
220
|
+
<div><odac var="richContent" raw /></div>
|
|
221
|
+
<div>{!! richContent !!}</div>
|
|
222
|
+
```
|
|
172
223
|
|
|
173
|
-
|
|
174
|
-
{!! htmlContent !!}
|
|
224
|
+
### Legacy Comment Syntax
|
|
175
225
|
|
|
176
|
-
|
|
226
|
+
```html
|
|
177
227
|
{{-- This is a comment --}}
|
|
178
228
|
```
|
|
179
229
|
|
|
180
|
-
**Note:**
|
|
230
|
+
**Note:** For new projects, prefer the `<!--odac ... odac-->` comment syntax for consistency.
|
|
181
231
|
|
|
@@ -311,18 +311,33 @@ module.exports = async function(Odac) {
|
|
|
311
311
|
</odac:if>
|
|
312
312
|
```
|
|
313
313
|
|
|
314
|
-
###
|
|
314
|
+
### Inline Syntax (`{{ }}` and `{!! !!}`)
|
|
315
315
|
|
|
316
|
-
|
|
316
|
+
ODAC provides an inline interpolation syntax that is equivalent to the `<odac var>` tag. Both compile to the same engine output and are equally supported.
|
|
317
|
+
|
|
318
|
+
**Use `{{ }}` when the variable appears inside HTML attributes or inline within text:**
|
|
317
319
|
|
|
318
320
|
```html
|
|
319
|
-
<!--
|
|
320
|
-
{{
|
|
321
|
-
{{ user.
|
|
321
|
+
<!-- Inside attributes — {{ }} keeps markup clean -->
|
|
322
|
+
<img src="{{ product.image }}" alt="{{ product.name }}">
|
|
323
|
+
<a href="/user/{{ user.id }}" class="btn {{ isActive ? 'active' : '' }}">Profile</a>
|
|
324
|
+
<input type="text" name="q" value="{{ searchQuery }}">
|
|
325
|
+
|
|
326
|
+
<!-- Inline text interpolation -->
|
|
327
|
+
<p>Welcome, {{ user.name }}. You have {{ notifications }} new messages.</p>
|
|
328
|
+
<span>${{ product.price }}</span>
|
|
322
329
|
|
|
323
|
-
<!-- Raw
|
|
330
|
+
<!-- Raw inline output (trusted content only) -->
|
|
324
331
|
{!! htmlContent !!}
|
|
325
332
|
{!! user.bio !!}
|
|
326
333
|
```
|
|
327
334
|
|
|
328
|
-
**
|
|
335
|
+
**Use `<odac var>` when the variable is the standalone content of an element:**
|
|
336
|
+
|
|
337
|
+
```html
|
|
338
|
+
<h1><odac var="pageTitle" /></h1>
|
|
339
|
+
<td><odac var="user.email" /></td>
|
|
340
|
+
<p><odac var="product.description" /></p>
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Both syntaxes are XSS-safe by default (HTML-escaped). The choice is purely about readability. See [Template Syntax Overview](./03-template-syntax.md) for the full selection guide.
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
## 🖼️ Image Optimization
|
|
2
|
+
|
|
3
|
+
ODAC provides automatic image optimization through the `<odac:img>` tag. Images are resized, converted to modern formats (WebP, AVIF), and cached — all on-demand, with zero configuration required.
|
|
4
|
+
|
|
5
|
+
> **Note:** Image optimization requires the `sharp` package. Install it with `npm install sharp`. Without it, `<odac:img>` gracefully falls back to a standard `<img>` tag with no processing.
|
|
6
|
+
|
|
7
|
+
### Basic Usage
|
|
8
|
+
|
|
9
|
+
The simplest usage — just provide a `src`. ODAC automatically converts the image to WebP:
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<odac:img src="/images/hero.jpg" />
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This is equivalent to writing:
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<img src="/_odac/img/hero-o-a1b2c3d4.webp">
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The processed image is cached after the first request. All subsequent requests are served instantly.
|
|
22
|
+
|
|
23
|
+
### Resize
|
|
24
|
+
|
|
25
|
+
Specify `width` and/or `height` to resize the image. Aspect ratio is always preserved — the image will never be stretched or enlarged beyond its original size:
|
|
26
|
+
|
|
27
|
+
```html
|
|
28
|
+
<!-- Resize by width only -->
|
|
29
|
+
<odac:img src="/images/hero.jpg" width="800" />
|
|
30
|
+
|
|
31
|
+
<!-- Resize by both dimensions (fits inside the box) -->
|
|
32
|
+
<odac:img src="/images/hero.jpg" width="800" height="600" />
|
|
33
|
+
|
|
34
|
+
<!-- Thumbnail -->
|
|
35
|
+
<odac:img src="/images/product.jpg" width="200" height="200" />
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Format Conversion
|
|
39
|
+
|
|
40
|
+
Convert to any modern format using the `format` attribute:
|
|
41
|
+
|
|
42
|
+
```html
|
|
43
|
+
<!-- WebP (default) -->
|
|
44
|
+
<odac:img src="/images/photo.png" format="webp" />
|
|
45
|
+
|
|
46
|
+
<!-- AVIF (best compression, newer browsers) -->
|
|
47
|
+
<odac:img src="/images/photo.png" format="avif" />
|
|
48
|
+
|
|
49
|
+
<!-- Keep as PNG -->
|
|
50
|
+
<odac:img src="/images/logo.png" format="png" />
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Supported formats:** `webp`, `avif`, `png`, `jpeg`, `tiff`
|
|
54
|
+
|
|
55
|
+
### Quality
|
|
56
|
+
|
|
57
|
+
Control the compression quality with the `quality` attribute (1–100, default: 80):
|
|
58
|
+
|
|
59
|
+
```html
|
|
60
|
+
<!-- High quality for hero images -->
|
|
61
|
+
<odac:img src="/images/banner.jpg" width="1200" quality="90" />
|
|
62
|
+
|
|
63
|
+
<!-- Lower quality for thumbnails (smaller file size) -->
|
|
64
|
+
<odac:img src="/images/thumb.jpg" width="100" quality="60" />
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Standard HTML Attributes
|
|
68
|
+
|
|
69
|
+
All standard `<img>` attributes are passed through as-is:
|
|
70
|
+
|
|
71
|
+
```html
|
|
72
|
+
<odac:img
|
|
73
|
+
src="/images/avatar.jpg"
|
|
74
|
+
width="64"
|
|
75
|
+
height="64"
|
|
76
|
+
alt="User avatar"
|
|
77
|
+
class="rounded-full"
|
|
78
|
+
loading="lazy"
|
|
79
|
+
decoding="async"
|
|
80
|
+
/>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Dynamic Source
|
|
84
|
+
|
|
85
|
+
Use template variables for dynamic image sources:
|
|
86
|
+
|
|
87
|
+
```html
|
|
88
|
+
<!-- From controller data -->
|
|
89
|
+
<odac:img src="{{ product.image }}" width="300" height="300" alt="{{ product.name }}" />
|
|
90
|
+
|
|
91
|
+
<!-- In a loop -->
|
|
92
|
+
<odac:for in="products" value="product">
|
|
93
|
+
<odac:img src="{{ product.thumbnail }}" width="200" alt="{{ product.name }}" />
|
|
94
|
+
</odac:for>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Practical Examples
|
|
98
|
+
|
|
99
|
+
#### Product Card
|
|
100
|
+
|
|
101
|
+
```html
|
|
102
|
+
<div class="product-card">
|
|
103
|
+
<odac:img
|
|
104
|
+
src="{{ product.image }}"
|
|
105
|
+
width="400"
|
|
106
|
+
height="300"
|
|
107
|
+
alt="{{ product.name }}"
|
|
108
|
+
class="product-image"
|
|
109
|
+
loading="lazy"
|
|
110
|
+
/>
|
|
111
|
+
<h3><odac var="product.name" /></h3>
|
|
112
|
+
<p>$<odac var="product.price" /></p>
|
|
113
|
+
</div>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### Hero Banner
|
|
117
|
+
|
|
118
|
+
```html
|
|
119
|
+
<section class="hero">
|
|
120
|
+
<odac:img
|
|
121
|
+
src="/images/hero.jpg"
|
|
122
|
+
width="1920"
|
|
123
|
+
height="600"
|
|
124
|
+
alt="Hero banner"
|
|
125
|
+
class="hero-image"
|
|
126
|
+
quality="90"
|
|
127
|
+
/>
|
|
128
|
+
</section>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
#### Avatar with Fallback
|
|
132
|
+
|
|
133
|
+
```html
|
|
134
|
+
<odac:if condition="user.avatar">
|
|
135
|
+
<odac:img src="{{ user.avatar }}" width="64" height="64" alt="Avatar" class="avatar" />
|
|
136
|
+
<odac:else>
|
|
137
|
+
<img src="/images/default-avatar.png" width="64" height="64" alt="Default avatar" class="avatar">
|
|
138
|
+
</odac:if>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### Image Gallery
|
|
142
|
+
|
|
143
|
+
```html
|
|
144
|
+
<div class="gallery">
|
|
145
|
+
<odac:for in="photos" value="photo">
|
|
146
|
+
<a href="{{ photo.url }}">
|
|
147
|
+
<odac:img
|
|
148
|
+
src="{{ photo.url }}"
|
|
149
|
+
width="400"
|
|
150
|
+
height="300"
|
|
151
|
+
alt="{{ photo.caption }}"
|
|
152
|
+
class="gallery-thumb"
|
|
153
|
+
loading="lazy"
|
|
154
|
+
/>
|
|
155
|
+
</a>
|
|
156
|
+
</odac:for>
|
|
157
|
+
</div>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Configuration
|
|
161
|
+
|
|
162
|
+
You can override the defaults in `odac.json`:
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"image": {
|
|
167
|
+
"format": "webp",
|
|
168
|
+
"quality": 80,
|
|
169
|
+
"maxDimension": 4096
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
| Option | Default | Description |
|
|
175
|
+
|--------|---------|-------------|
|
|
176
|
+
| `format` | `webp` | Default output format when `format` is not specified in the tag |
|
|
177
|
+
| `quality` | `80` | Default compression quality (1–100) |
|
|
178
|
+
| `maxDimension` | `4096` | Maximum allowed width or height in pixels |
|
|
179
|
+
|
|
180
|
+
### How It Works
|
|
181
|
+
|
|
182
|
+
1. The `<odac:img>` tag is compiled at template render time.
|
|
183
|
+
2. ODAC checks the source file's modification time (`mtime`) and includes it in the hash. This means when you update a source image, the URL automatically changes — no manual cache busting needed.
|
|
184
|
+
3. On the first request, ODAC processes the source image using `sharp` and saves the result to `storage/.cache/img/`.
|
|
185
|
+
4. The `<img>` tag in the HTML points to `/_odac/img/{name}-{dimension}-{hash}.{ext}` — an internal route that serves the cached file. The filename includes the original image name and dimension for easy debugging.
|
|
186
|
+
5. All subsequent requests hit the cache directly, with `Cache-Control: immutable` headers for maximum browser caching.
|
|
187
|
+
|
|
188
|
+
> **Cache Busting:** You don't need to rename files or add query strings. Simply replace the source image in `public/` and the next page render will produce a new URL with a different hash, forcing browsers and CDNs to fetch the updated version.
|
|
189
|
+
|
|
190
|
+
### Best Practices
|
|
191
|
+
|
|
192
|
+
- Always set `alt` for accessibility.
|
|
193
|
+
- Use `loading="lazy"` for below-the-fold images to improve page load performance.
|
|
194
|
+
- Set explicit `width` and `height` to prevent layout shift (CLS).
|
|
195
|
+
- Keep original high-resolution images in `public/` — let ODAC handle the downsizing.
|
|
196
|
+
- Use `avif` for the best compression ratio on modern browsers; use `webp` for broader compatibility.
|
|
197
|
+
- For programmatic URL access in controllers (JSON APIs, CSS backgrounds, emails), see [`Odac.image()`](../05-controllers/02-your-trusty-odac-assistant.md#image-processing).
|
|
@@ -6,6 +6,7 @@ Odac framework includes a built-in AJAX navigation system that enables smooth, s
|
|
|
6
6
|
|
|
7
7
|
- **Zero Configuration**: Works automatically with all internal links (`/`)
|
|
8
8
|
- **Smooth Transitions**: Load only specific page sections without full page reload
|
|
9
|
+
- **Native View Transitions**: Automatic browser View Transition API support via `odac-transition` attribute
|
|
9
10
|
- **History API Integration**: Browser back/forward buttons work seamlessly
|
|
10
11
|
- **Automatic Token Management**: CSRF tokens are handled automatically
|
|
11
12
|
- **Progressive Enhancement**: Falls back to normal navigation if JavaScript fails
|
|
@@ -178,6 +179,27 @@ module.exports = function (Odac) {
|
|
|
178
179
|
5. Browser URL updates via History API
|
|
179
180
|
6. Page-specific callbacks execute
|
|
180
181
|
|
|
182
|
+
### View Transition Load (with `odac-transition` elements)
|
|
183
|
+
|
|
184
|
+
When elements with `odac-transition` attribute exist on the page and the browser supports the View Transition API, ODAC uses native transitions instead of fade:
|
|
185
|
+
|
|
186
|
+
1. User clicks `<a href="/about">`
|
|
187
|
+
2. ODAC assigns `view-transition-name` to all `odac-transition` elements (old state snapshot)
|
|
188
|
+
3. AJAX request is sent (same as above)
|
|
189
|
+
4. `document.startViewTransition()` is called — browser captures old state
|
|
190
|
+
5. DOM is updated with new content inside the transition callback
|
|
191
|
+
6. New `odac-transition` elements receive their names
|
|
192
|
+
7. Browser animates between old and new snapshots
|
|
193
|
+
8. Transition names are cleaned up after completion
|
|
194
|
+
|
|
195
|
+
No configuration needed — just add the attribute to your HTML:
|
|
196
|
+
|
|
197
|
+
```html
|
|
198
|
+
<header odac-transition="header">{{ HEADER }}</header>
|
|
199
|
+
<main>{{ CONTENT }}</main>
|
|
200
|
+
<img odac-transition="hero" src="/hero.jpg" alt="Hero" />
|
|
201
|
+
```
|
|
202
|
+
|
|
181
203
|
**Key Points:**
|
|
182
204
|
- The `output` keys in the JSON response match the lowercase keys from `Odac.View.set()` in your controller
|
|
183
205
|
- These keys correspond to UPPERCASE placeholders in your skeleton (e.g., `content` → `{{ CONTENT }}`)
|