okno 1.0.0-alpha.1 → 1.0.0-beta5
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/README.md +695 -0
- package/dist/adapters/astro.d.ts +3 -0
- package/dist/adapters/astro.d.ts.map +1 -0
- package/dist/adapters/astro.js +32 -0
- package/dist/adapters/index.d.ts +3 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/vite.d.ts +4 -0
- package/dist/adapters/vite.d.ts.map +1 -0
- package/dist/adapters/vite.js +21 -0
- package/dist/client-CeppRtrT.js +55 -0
- package/dist/core/__tests__/builders.test.d.ts +2 -0
- package/dist/core/__tests__/builders.test.d.ts.map +1 -0
- package/dist/core/__tests__/fields.test.d.ts +2 -0
- package/dist/core/__tests__/fields.test.d.ts.map +1 -0
- package/dist/core/__tests__/runtime.test.d.ts +2 -0
- package/dist/core/__tests__/runtime.test.d.ts.map +1 -0
- package/dist/core/builders/collection.d.ts +9 -0
- package/dist/core/builders/collection.d.ts.map +1 -0
- package/dist/core/builders/component.d.ts +26 -0
- package/dist/core/builders/component.d.ts.map +1 -0
- package/dist/core/builders/global.d.ts +8 -0
- package/dist/core/builders/global.d.ts.map +1 -0
- package/dist/core/builders/group.d.ts +18 -0
- package/dist/core/builders/group.d.ts.map +1 -0
- package/dist/core/builders/index.d.ts +8 -0
- package/dist/core/builders/index.d.ts.map +1 -0
- package/dist/core/builders/page.d.ts +9 -0
- package/dist/core/builders/page.d.ts.map +1 -0
- package/dist/core/builders/utils.d.ts +8 -0
- package/dist/core/builders/utils.d.ts.map +1 -0
- package/dist/core/components/index.d.ts +3 -0
- package/dist/core/components/index.d.ts.map +1 -0
- package/dist/core/components/link.d.ts +12 -0
- package/dist/core/components/link.d.ts.map +1 -0
- package/dist/core/components/meta.d.ts +12 -0
- package/dist/core/components/meta.d.ts.map +1 -0
- package/dist/core/field-result.d.ts +26 -0
- package/dist/core/field-result.d.ts.map +1 -0
- package/dist/core/fields/boolean.d.ts +12 -0
- package/dist/core/fields/boolean.d.ts.map +1 -0
- package/dist/core/fields/date.d.ts +14 -0
- package/dist/core/fields/date.d.ts.map +1 -0
- package/dist/core/fields/enum.d.ts +14 -0
- package/dist/core/fields/enum.d.ts.map +1 -0
- package/dist/core/fields/index.d.ts +12 -0
- package/dist/core/fields/index.d.ts.map +1 -0
- package/dist/core/fields/media.d.ts +33 -0
- package/dist/core/fields/media.d.ts.map +1 -0
- package/dist/core/fields/number.d.ts +16 -0
- package/dist/core/fields/number.d.ts.map +1 -0
- package/dist/core/fields/reference.d.ts +12 -0
- package/dist/core/fields/reference.d.ts.map +1 -0
- package/dist/core/fields/richtext.d.ts +13 -0
- package/dist/core/fields/richtext.d.ts.map +1 -0
- package/dist/core/fields/slug.d.ts +10 -0
- package/dist/core/fields/slug.d.ts.map +1 -0
- package/dist/core/fields/string.d.ts +21 -0
- package/dist/core/fields/string.d.ts.map +1 -0
- package/dist/core/fields/types.d.ts +101 -0
- package/dist/core/fields/types.d.ts.map +1 -0
- package/dist/core/fields/user.d.ts +13 -0
- package/dist/core/fields/user.d.ts.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +23 -0
- package/dist/core/okno.d.ts +21 -0
- package/dist/core/okno.d.ts.map +1 -0
- package/dist/core/runtime.d.ts +9 -0
- package/dist/core/runtime.d.ts.map +1 -0
- package/dist/core/schema.d.ts +2 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/editor/index.js +1527 -19113
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -2
- package/dist/meta-D-_nuC5Y.js +548 -0
- package/dist/server/client.d.ts +29 -0
- package/dist/server/client.d.ts.map +1 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +6 -0
- package/dist/server/types.d.ts +48 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/types/global.d.ts +54 -0
- package/dist/types/global.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/path.d.ts +35 -0
- package/dist/types/path.d.ts.map +1 -0
- package/dist/types/query.d.ts +31 -0
- package/dist/types/query.d.ts.map +1 -0
- package/dist/vite.config.d.ts +3 -0
- package/dist/vite.config.d.ts.map +1 -0
- package/dist/vite.dev.config.d.ts +3 -0
- package/dist/vite.dev.config.d.ts.map +1 -0
- package/package.json +44 -35
- package/dist/codegen/generate.d.ts +0 -6
- package/dist/codegen/generate.d.ts.map +0 -1
- package/dist/plugin-C4ZJr14i.js +0 -440
- package/dist/runtime/wrap.d.ts +0 -11
- package/dist/runtime/wrap.d.ts.map +0 -1
- package/dist/types/fields.d.ts +0 -73
- package/dist/types/fields.d.ts.map +0 -1
- package/dist/types/schema.d.ts +0 -47
- package/dist/types/schema.d.ts.map +0 -1
- package/dist/vite/dev-server.d.ts +0 -8
- package/dist/vite/dev-server.d.ts.map +0 -1
- package/dist/vite/index.d.ts +0 -2
- package/dist/vite/index.d.ts.map +0 -1
- package/dist/vite/index.js +0 -4
- package/dist/vite/plugin.d.ts +0 -4
- package/dist/vite/plugin.d.ts.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
# okno Documentation
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
okno is a developer-friendly headless CMS with live editing capabilities for static and dynamic sites.
|
|
6
|
+
|
|
7
|
+
### Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install okno
|
|
11
|
+
npm install okno/astro # Framework adapter
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Quick Start
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
// /okno.ts
|
|
19
|
+
import { schema, collection, page, string, richtext } from "okno"
|
|
20
|
+
|
|
21
|
+
export const okno= schema({
|
|
22
|
+
articles: collection({
|
|
23
|
+
title: string(),
|
|
24
|
+
content: richtext()
|
|
25
|
+
}),
|
|
26
|
+
|
|
27
|
+
home: page({
|
|
28
|
+
heroTitle: string()
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// Use in your components
|
|
33
|
+
import { okno } from "@/okno"
|
|
34
|
+
|
|
35
|
+
const articles = okno("articles")
|
|
36
|
+
const homeTitle = okno("home.heroTitle")
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Schema
|
|
43
|
+
|
|
44
|
+
### Types
|
|
45
|
+
|
|
46
|
+
okno has three content types:
|
|
47
|
+
|
|
48
|
+
**Collections** - Multiple items (e.g., blog posts, products)
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
articles: collection({
|
|
52
|
+
title: string(),
|
|
53
|
+
slug: slug("title"),
|
|
54
|
+
content: richtext()
|
|
55
|
+
}).draftable()
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Pages** - Single items with optional routes (e.g., Home, About)
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
home: page({
|
|
63
|
+
heroTitle: string(),
|
|
64
|
+
heroImage: media().image()
|
|
65
|
+
}).route('/')
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Globals** - Site-wide singletons (e.g., Navbar, Footer, Settings)
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
navbar: global({
|
|
73
|
+
logo: media().image(),
|
|
74
|
+
links: string().array()
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Fields
|
|
80
|
+
|
|
81
|
+
### Basic Fields
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
// String types
|
|
85
|
+
string() // Basic text field, required by default
|
|
86
|
+
string().optional() // Optional text field
|
|
87
|
+
string().max(200) // Length limit
|
|
88
|
+
string().min(10) // Minimum length
|
|
89
|
+
string().email() // Email validation
|
|
90
|
+
string().url() // URL validation
|
|
91
|
+
string().phone() // Phone validation
|
|
92
|
+
string().pattern(/^[a-z0-9-]+$/) // Regex pattern
|
|
93
|
+
string().array() // Array of strings
|
|
94
|
+
string().default('hello') // Default value
|
|
95
|
+
string().help("Enter your full name") // Help text in editor
|
|
96
|
+
string().protected() // Not editable in UI
|
|
97
|
+
|
|
98
|
+
// Numbers
|
|
99
|
+
number() // Numeric field
|
|
100
|
+
number().min(0).max(100) // With range
|
|
101
|
+
number().default(0) // Default value
|
|
102
|
+
|
|
103
|
+
// Boolean
|
|
104
|
+
boolean() // True/false
|
|
105
|
+
boolean().default(false)
|
|
106
|
+
|
|
107
|
+
// Enums
|
|
108
|
+
enum(['draft', 'published', 'archived'])
|
|
109
|
+
enum(['javascript', 'svelte', 'typescript']).multiple() // Multi-select
|
|
110
|
+
|
|
111
|
+
// Dates
|
|
112
|
+
date() // Date field
|
|
113
|
+
date().default(() => new Date()) // Dynamic default
|
|
114
|
+
|
|
115
|
+
// Rich content
|
|
116
|
+
richtext() // HTML content field
|
|
117
|
+
richtext().slots(['price', 'date']) // With dynamic slots
|
|
118
|
+
markdown() // TODO: Markdown content field (future)
|
|
119
|
+
|
|
120
|
+
// Media
|
|
121
|
+
media() // Accept any media type
|
|
122
|
+
media().image() // Only images
|
|
123
|
+
media().video() // Only videos
|
|
124
|
+
media().audio() // Only audio
|
|
125
|
+
media().document() // Only documents (PDF, Word, etc)
|
|
126
|
+
|
|
127
|
+
// Media with multiple types and rules
|
|
128
|
+
media()
|
|
129
|
+
.image({ maxWidth: 1920, maxHeight: 1080, format: ['jpg', 'png', 'webp'] })
|
|
130
|
+
.video({ maxSize: '100MB', maxDuration: 300 })
|
|
131
|
+
.document({ maxSize: '10MB', accept: ['pdf', 'docx'] })
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### System Fields
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
slug("fieldName") // Auto-generated from field
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Built-in Fields (Collections Only)
|
|
143
|
+
|
|
144
|
+
All **collection items** automatically include:
|
|
145
|
+
|
|
146
|
+
- `id` - unique identifier
|
|
147
|
+
- `slug` - URL-friendly key (defined with `slug("fieldName")`)
|
|
148
|
+
- `created` - timestamp
|
|
149
|
+
- `updated` - timestamp
|
|
150
|
+
- `published` - timestamp or null (for draftable collections)
|
|
151
|
+
- `createdBy` - user reference
|
|
152
|
+
- `updatedBy` - user reference
|
|
153
|
+
- `publishedBy` - user reference or null
|
|
154
|
+
|
|
155
|
+
Pages and globals do not have these fields - they are singletons identified by their key name.
|
|
156
|
+
|
|
157
|
+
### Groups
|
|
158
|
+
|
|
159
|
+
Organize related fields in the editor UI:
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
article: collection({
|
|
163
|
+
title: string(),
|
|
164
|
+
content: richtext(),
|
|
165
|
+
|
|
166
|
+
seo: group({
|
|
167
|
+
metaTitle: string().max(60).help("Appears in search results"),
|
|
168
|
+
metaDescription: string().max(160),
|
|
169
|
+
ogImage: media().image()
|
|
170
|
+
}),
|
|
171
|
+
|
|
172
|
+
advanced: group({
|
|
173
|
+
customCSS: string().optional(),
|
|
174
|
+
trackingCode: string().optional()
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
// Access with dot notation
|
|
179
|
+
okno("articles.my-post.seo.metaTitle")
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Conditional Fields
|
|
184
|
+
|
|
185
|
+
Show/hide fields based on other field values:
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
post: collection({
|
|
189
|
+
type: enum(['article', 'video', 'podcast']),
|
|
190
|
+
|
|
191
|
+
// Only show when type is 'article'
|
|
192
|
+
content: richtext().when('type', 'article'),
|
|
193
|
+
|
|
194
|
+
// Only show when type is 'video'
|
|
195
|
+
videoUrl: string().url().when('type', 'video'),
|
|
196
|
+
videoThumbnail: media().image().when('type', 'video'),
|
|
197
|
+
|
|
198
|
+
// Show when type is either 'video' or 'podcast'
|
|
199
|
+
duration: number().min(0).when('type', ['video', 'podcast'])
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Groups can also be conditional:
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
article: collection({
|
|
208
|
+
type: enum(['article', 'video', 'podcast']),
|
|
209
|
+
premium: boolean().default(false),
|
|
210
|
+
|
|
211
|
+
// Show entire video group only when type is 'video'
|
|
212
|
+
video: group({
|
|
213
|
+
url: string().url().required(),
|
|
214
|
+
thumbnail: media().image(),
|
|
215
|
+
duration: number().min(0),
|
|
216
|
+
subtitles: string().optional()
|
|
217
|
+
}).when('type', 'video'),
|
|
218
|
+
|
|
219
|
+
// Show entire SEO group only when premium is true
|
|
220
|
+
seo: group({
|
|
221
|
+
metaTitle: string().max(60),
|
|
222
|
+
metaDescription: string().max(160),
|
|
223
|
+
ogImage: media().image()
|
|
224
|
+
}).when('premium', true)
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Default Values
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
article: collection({
|
|
233
|
+
status: enum(['draft', 'published']).default('draft'),
|
|
234
|
+
views: number().default(0),
|
|
235
|
+
featured: boolean().default(false),
|
|
236
|
+
publishedAt: date().default(() => new Date()),
|
|
237
|
+
tags: enum(['js', 'css', 'html']).multiple().default(['js'])
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### References
|
|
243
|
+
|
|
244
|
+
Reference other collections:
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
reference("collection") // Single reference
|
|
248
|
+
reference("collection").multiple() // Multiple references
|
|
249
|
+
|
|
250
|
+
// Example
|
|
251
|
+
articles: collection({
|
|
252
|
+
title: string(),
|
|
253
|
+
category: reference("categories"),
|
|
254
|
+
relatedPosts: reference("articles").multiple()
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Users
|
|
260
|
+
|
|
261
|
+
The `users` collection is special - it links to PocketBase authentication.
|
|
262
|
+
|
|
263
|
+
### Defining Users
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
users: collection({
|
|
267
|
+
bio: string().optional(),
|
|
268
|
+
avatar: media().image().optional(),
|
|
269
|
+
website: string().url().optional()
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
When users authenticate, okno auto-creates their `users` record linked to their PocketBase auth user.
|
|
275
|
+
|
|
276
|
+
### User References
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
user() // Reference to users collection
|
|
280
|
+
user().multiple() // Multiple user references
|
|
281
|
+
user().default("current") // Default to logged-in user
|
|
282
|
+
|
|
283
|
+
// Example
|
|
284
|
+
articles: collection({
|
|
285
|
+
title: string(),
|
|
286
|
+
author: user().default("current"),
|
|
287
|
+
contributors: user().multiple()
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Localization
|
|
293
|
+
|
|
294
|
+
Enable multiple languages:
|
|
295
|
+
|
|
296
|
+
```tsx
|
|
297
|
+
export const okno = schema({
|
|
298
|
+
articles: collection({
|
|
299
|
+
title: string().localized(),
|
|
300
|
+
slug: slug("title"), // not localized
|
|
301
|
+
content: richtext().localized(),
|
|
302
|
+
featuredImage: media().image() // same for all locales
|
|
303
|
+
}).localized()
|
|
304
|
+
})
|
|
305
|
+
.locales({
|
|
306
|
+
en: locale({ name: "English", default: true }),
|
|
307
|
+
es: locale({ name: "Español" }),
|
|
308
|
+
"cs-CZ": locale({ name: "Čeština" })
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Content structure with locales:
|
|
314
|
+
|
|
315
|
+
```tsx
|
|
316
|
+
{
|
|
317
|
+
id: "123",
|
|
318
|
+
slug: "my-article",
|
|
319
|
+
title: {
|
|
320
|
+
en: "My Article",
|
|
321
|
+
es: "Mi Artículo",
|
|
322
|
+
"cs-CZ": "Můj článek"
|
|
323
|
+
},
|
|
324
|
+
content: {
|
|
325
|
+
en: "English content",
|
|
326
|
+
es: "Contenido en español"
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Query with locale:
|
|
333
|
+
|
|
334
|
+
```tsx
|
|
335
|
+
// Specific locale
|
|
336
|
+
okno("articles", { locale: "es" })
|
|
337
|
+
|
|
338
|
+
// All locales
|
|
339
|
+
okno("articles", { locale: "*" }) // default
|
|
340
|
+
|
|
341
|
+
// With fallback
|
|
342
|
+
okno("articles", { locale: "es", fallback: ["en"] })
|
|
343
|
+
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Config
|
|
347
|
+
|
|
348
|
+
Configuration is chained after schema and/or locales:
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
export const okno = schema({
|
|
352
|
+
users: collection({...}),
|
|
353
|
+
articles: collection({...})
|
|
354
|
+
})
|
|
355
|
+
.locales({
|
|
356
|
+
en: locale({ name: "English", default: true })
|
|
357
|
+
})
|
|
358
|
+
.config({
|
|
359
|
+
siteUrl: 'https://mysite.com' // Required for user invitations
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### User Invitations
|
|
365
|
+
|
|
366
|
+
The `siteUrl` enables user invitations in the editor:
|
|
367
|
+
|
|
368
|
+
1. Owner clicks "Invite user" in editor
|
|
369
|
+
2. Enters email and role (owner/editor/viewer)
|
|
370
|
+
3. Invitation email sent: `https://mysite.com/invite?token=xyz`
|
|
371
|
+
4. Recipient registers/logs in via PocketBase
|
|
372
|
+
5. okno auto-creates their `users` record
|
|
373
|
+
6. User has access to the space
|
|
374
|
+
|
|
375
|
+
Without `siteUrl`, invite button is disabled with a setup warning.
|
|
376
|
+
|
|
377
|
+
### Forms
|
|
378
|
+
|
|
379
|
+
_Forms are not the current priority._
|
|
380
|
+
|
|
381
|
+
Define forms in your schema:
|
|
382
|
+
|
|
383
|
+
```tsx
|
|
384
|
+
contact: form({
|
|
385
|
+
name: string().required(),
|
|
386
|
+
email: string().email().required(),
|
|
387
|
+
password: string().required(),
|
|
388
|
+
message: textarea().required()
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Spread forms and fields for automatic handling:
|
|
394
|
+
|
|
395
|
+
```tsx
|
|
396
|
+
<form {...okno("forms.contact")}>
|
|
397
|
+
{/* Fields auto-detect type from schema */}
|
|
398
|
+
<input {...okno("forms.contact.name")} />
|
|
399
|
+
{/* → type="text", name="name", required, etc. */}
|
|
400
|
+
|
|
401
|
+
<input {...okno("forms.contact.email")} />
|
|
402
|
+
{/* → type="email", name="email", required, etc. */}
|
|
403
|
+
|
|
404
|
+
{/* Override type when needed */}
|
|
405
|
+
<input {...okno("forms.contact.password")} type="password" />
|
|
406
|
+
{/* → type="password" (overridden), name="password", required, etc. */}
|
|
407
|
+
|
|
408
|
+
<textarea {...okno("forms.contact.message")}></textarea>
|
|
409
|
+
|
|
410
|
+
<button type="submit">Send</button>
|
|
411
|
+
</form>
|
|
412
|
+
|
|
413
|
+
{/* Error handling per field */}
|
|
414
|
+
{#each okno("forms.contact.email").errors() as error}
|
|
415
|
+
<span class="error">{error.message}</span>
|
|
416
|
+
{/each}
|
|
417
|
+
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
The form spread provides `onSubmit` that prevents default and POSTs to custom PocketBase endpoint.
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## Query
|
|
425
|
+
|
|
426
|
+
### Basic
|
|
427
|
+
|
|
428
|
+
```tsx
|
|
429
|
+
import { okno } from "@/okno"
|
|
430
|
+
|
|
431
|
+
// Get single item by key
|
|
432
|
+
okno("home.heroTitle")
|
|
433
|
+
|
|
434
|
+
// Get collection
|
|
435
|
+
okno("articles")
|
|
436
|
+
|
|
437
|
+
// Get by slug
|
|
438
|
+
okno("articles", { slug: "getting-started" })
|
|
439
|
+
|
|
440
|
+
// Get by ID
|
|
441
|
+
okno("articles", { id: "rec_123" })
|
|
442
|
+
|
|
443
|
+
// Get specific fields
|
|
444
|
+
okno("articles", { select: ["title", "slug", "excerpt"] })
|
|
445
|
+
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Filters
|
|
449
|
+
|
|
450
|
+
```tsx
|
|
451
|
+
// Simple filters
|
|
452
|
+
okno("articles", {
|
|
453
|
+
where: {
|
|
454
|
+
featured: true,
|
|
455
|
+
category: "tutorial"
|
|
456
|
+
}
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
// Sorting
|
|
460
|
+
okno("articles", {
|
|
461
|
+
sort: "-publishedAt" // Descending
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
okno("articles", {
|
|
465
|
+
sort: "title" // Ascending
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
// Limit
|
|
469
|
+
okno("articles", {
|
|
470
|
+
limit: 10
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
// Combine filters, sorting, and limit
|
|
474
|
+
okno("articles", {
|
|
475
|
+
where: { featured: true, category: "tutorial" },
|
|
476
|
+
sort: "-publishedAt",
|
|
477
|
+
limit: 10,
|
|
478
|
+
select: ["title", "slug", "excerpt"]
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
//TODO: pagination?
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Drafts
|
|
485
|
+
|
|
486
|
+
For draftable collections:
|
|
487
|
+
|
|
488
|
+
```tsx
|
|
489
|
+
articles: collection({...}).draftable()
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
Content can be in three states:
|
|
493
|
+
|
|
494
|
+
1. **Draft** - Created but not published
|
|
495
|
+
2. **Published** - Live content
|
|
496
|
+
3. **Published with edits** - Has unpublished changes
|
|
497
|
+
|
|
498
|
+
Query drafts:
|
|
499
|
+
|
|
500
|
+
```tsx
|
|
501
|
+
okno("articles") // Published only (default)
|
|
502
|
+
okno("articles", { drafts: true }) // Include drafts
|
|
503
|
+
okno("articles", { drafts: 'only' }) // Only drafts
|
|
504
|
+
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### Slots
|
|
508
|
+
|
|
509
|
+
Define dynamic content placeholders:
|
|
510
|
+
|
|
511
|
+
```tsx
|
|
512
|
+
articles: collection({
|
|
513
|
+
content: richtext().slots(['price', 'count', 'date'])
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
// TODO: non defined slots, just .slots() and create them on the go.
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
Content with placeholders:
|
|
520
|
+
|
|
521
|
+
```tsx
|
|
522
|
+
{
|
|
523
|
+
content: "We have {{count}} items starting at {{price}}. Sale ends {{date}}!"
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
Populate slots dynamically:
|
|
529
|
+
|
|
530
|
+
```tsx
|
|
531
|
+
okno("sale.content", {
|
|
532
|
+
slots: {
|
|
533
|
+
count: inventory.length,
|
|
534
|
+
price: "$9.99",
|
|
535
|
+
date: "March 31st"
|
|
536
|
+
}
|
|
537
|
+
})
|
|
538
|
+
// Returns: "We have 42 items starting at $9.99. Sale ends March 31st!"
|
|
539
|
+
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
---
|
|
543
|
+
|
|
544
|
+
## Editor
|
|
545
|
+
|
|
546
|
+
### Framework Support
|
|
547
|
+
|
|
548
|
+
```bash
|
|
549
|
+
npm install okno
|
|
550
|
+
npm install okno/astro # or okno/vite, okno/next, etc.
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
Framework adapters:
|
|
554
|
+
|
|
555
|
+
```tsx
|
|
556
|
+
// astro.config.mjs
|
|
557
|
+
import { okno } from "okno/astro"
|
|
558
|
+
|
|
559
|
+
// vite.config.js
|
|
560
|
+
import { okno } from "okno/vite"
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
The editor is imported separately and handles live editing in the browser.
|
|
564
|
+
|
|
565
|
+
### Content Rendering
|
|
566
|
+
|
|
567
|
+
### Editable Content
|
|
568
|
+
|
|
569
|
+
```
|
|
570
|
+
<!-- Text content needs set:html for edit spans -->
|
|
571
|
+
<h1 set:html={okno("home.title")} />
|
|
572
|
+
|
|
573
|
+
<!-- Images don't need special handling -->
|
|
574
|
+
<img {...okno("home.heroImage")} />
|
|
575
|
+
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Non-editable Context
|
|
579
|
+
|
|
580
|
+
```
|
|
581
|
+
<!-- Get plain text without HTML -->
|
|
582
|
+
<meta name="description" content={okno("home.title", { plain: true })} />
|
|
583
|
+
|
|
584
|
+
<!-- Get raw HTML without edit spans -->
|
|
585
|
+
<div set:html={okno("home.content", { raw: true })} />
|
|
586
|
+
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
### Data Attributes
|
|
590
|
+
|
|
591
|
+
All non-text content automatically includes `data-okno` attributes for editor targeting:
|
|
592
|
+
|
|
593
|
+
```tsx
|
|
594
|
+
// Images include data-okno
|
|
595
|
+
okno("home.heroImage")
|
|
596
|
+
// Returns:
|
|
597
|
+
{
|
|
598
|
+
src: "/hero.jpg",
|
|
599
|
+
alt: "Hero image",
|
|
600
|
+
width: 1920,
|
|
601
|
+
height: 1080,
|
|
602
|
+
"data-okno": "home.heroImage"
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Just spread and it works
|
|
606
|
+
<img {...okno("home.heroImage")} />
|
|
607
|
+
// Renders: <img src="..." data-okno="home.heroImage" />
|
|
608
|
+
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### Collection Items
|
|
612
|
+
|
|
613
|
+
For collections, each item's fields include the specific item ID:
|
|
614
|
+
|
|
615
|
+
```tsx
|
|
616
|
+
const articles = okno("articles")
|
|
617
|
+
|
|
618
|
+
articles.map(article => (
|
|
619
|
+
<article>
|
|
620
|
+
{/* Text fields are wrapped automatically */}
|
|
621
|
+
<h2 set:html={article.title} />
|
|
622
|
+
|
|
623
|
+
{/* Images include the specific article ID */}
|
|
624
|
+
<img {...article.featuredImage} />
|
|
625
|
+
{/* Renders: <img src="..." data-okno="articles.abc-123.featuredImage" /> */}
|
|
626
|
+
</article>
|
|
627
|
+
))
|
|
628
|
+
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
### Data Attribute Format
|
|
632
|
+
|
|
633
|
+
- **Direct queries**: `data-okno="home.heroImage"`
|
|
634
|
+
- **Collection items**: `data-okno="articles.{id}.featuredImage"`
|
|
635
|
+
- **Nested fields**: `data-okno="home.hero.backgroundImage"`
|
|
636
|
+
|
|
637
|
+
This allows the editor to precisely target any piece of content for editing.
|
|
638
|
+
|
|
639
|
+
---
|
|
640
|
+
|
|
641
|
+
## TODO: Future Features
|
|
642
|
+
|
|
643
|
+
The following features are planned but not yet implemented:
|
|
644
|
+
|
|
645
|
+
### Rich Text Editor Configuration
|
|
646
|
+
|
|
647
|
+
- Toolbar customization
|
|
648
|
+
- Custom blocks/components
|
|
649
|
+
- Image upload handling within richtext
|
|
650
|
+
- Embed support (YouTube, Twitter, etc.)
|
|
651
|
+
|
|
652
|
+
### Media Upload Configuration
|
|
653
|
+
|
|
654
|
+
- Global upload limits (size, dimensions)
|
|
655
|
+
- Storage provider configuration
|
|
656
|
+
- Image optimization settings
|
|
657
|
+
- CDN integration
|
|
658
|
+
|
|
659
|
+
### Collection Ordering
|
|
660
|
+
|
|
661
|
+
```tsx
|
|
662
|
+
articles: collection({...}).sortable() // Manual drag-and-drop ordering
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
### Custom Validation Messages
|
|
666
|
+
|
|
667
|
+
```tsx
|
|
668
|
+
email: string().email("Please enter a valid email address")
|
|
669
|
+
age: number().min(18, "Must be 18 or older")
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
### Full-Text Search
|
|
673
|
+
|
|
674
|
+
```tsx
|
|
675
|
+
okno("articles", { search: "keyword" }) // Search across all text fields
|
|
676
|
+
okno("articles", { search: "keyword", fields: ["title", "content"] })
|
|
677
|
+
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
### Webhooks
|
|
681
|
+
|
|
682
|
+
```tsx
|
|
683
|
+
articles: collection({...})
|
|
684
|
+
.webhook({
|
|
685
|
+
onPublish: 'https://api.example.com/notify',
|
|
686
|
+
onUpdate: 'https://api.example.com/sync'
|
|
687
|
+
})
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
### Draft Preview URLs
|
|
691
|
+
|
|
692
|
+
```tsx
|
|
693
|
+
articles: collection({...})
|
|
694
|
+
.preview((item) => `/preview/${item.slug}?token=${item.previewToken}`)
|
|
695
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"astro.d.ts","sourceRoot":"","sources":["../../adapters/astro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAK9C,wBAAgB,IAAI,IAAI,gBAAgB,CA4DvC"}
|