@kyro-cms/admin 0.2.10 → 0.3.0
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 +46 -272
- package/package.json +32 -7
- package/src/blocks/examples/sample-block-2.tsx +27 -0
- package/src/blocks/examples/sample-block.tsx +26 -0
- package/src/blocks/index.ts +14 -0
- package/src/blocks/registry.ts +38 -0
- package/src/blocks/types.ts +23 -0
- package/src/components/Admin.tsx +1 -1
- package/src/components/ApiKeysManager.tsx +1 -1
- package/src/components/AuditLogsPage.tsx +1 -1
- package/src/components/AutoForm.tsx +2 -2
- package/src/components/BrandingHub.tsx +1 -1
- package/src/components/CreateView.tsx +1 -1
- package/src/components/DetailView.tsx +1 -1
- package/src/components/DeveloperCenter.tsx +1 -1
- package/src/components/EnhancedListView.tsx +1 -1
- package/src/components/ListView.tsx +1 -1
- package/src/components/LoginPage.tsx +1 -1
- package/src/components/MediaGallery.tsx +1 -1
- package/src/components/UserManagement.tsx +1 -1
- package/src/components/WebhookManager.tsx +2 -2
- package/src/components/fields/RelationshipBlockField.tsx +1 -1
- package/src/components/fields/RelationshipField.tsx +1 -1
- package/src/components/fields/UploadField.tsx +1 -6
- package/src/components/ui/CommandPalette.tsx +1 -1
- package/src/fields/examples/sample-field-2.tsx +30 -0
- package/src/fields/examples/sample-field.tsx +30 -0
- package/src/fields/index.ts +33 -0
- package/src/fields/registry.tsx +46 -0
- package/src/fields/types.ts +24 -0
- package/src/hooks/data.ts +116 -0
- package/src/hooks/examples/sample-hook-2.ts +13 -0
- package/src/hooks/examples/sample-hook.ts +12 -0
- package/src/hooks/index.ts +19 -0
- package/src/hooks/lifecycle.ts +81 -0
- package/src/hooks/types.ts +40 -0
- package/src/index.ts +78 -0
- package/src/integration.ts +52 -0
- package/src/pages/api/[collection]/[id]/publish.ts +2 -2
- package/src/pages/api/[collection]/[id]/unpublish.ts +2 -2
- package/src/pages/api/[collection]/[id]/versions.ts +1 -1
- package/src/pages/api/[collection]/[id].ts +2 -2
- package/src/pages/api/[collection]/index.ts +2 -2
- package/src/pages/api/collections.ts +1 -1
- package/src/pages/api/globals/[slug].ts +2 -2
- package/src/pages/api/graphql.ts +3 -3
- package/src/pages/api/media/folders.ts +1 -1
- package/src/pages/api/media/index.ts +1 -1
- package/src/pages/api/media/resize.ts +1 -1
- package/src/pages/api/slug-availability.ts +2 -2
- package/src/pages/api/storage-config.ts +1 -1
- package/src/pages/api/storage-status.ts +1 -1
- package/src/pages/api/upload.ts +1 -1
- package/src/plugins/examples/sample-plugin-2.ts +21 -0
- package/src/plugins/examples/sample-plugin.ts +21 -0
- package/src/plugins/index.ts +10 -0
- package/src/plugins/registry.ts +36 -0
- package/src/plugins/types.ts +22 -0
- package/src/theme/ThemeProvider.tsx +238 -0
- package/src/theme/index.ts +20 -0
- package/src/theme/tokens.ts +222 -0
- package/src/components/Modal.tsx +0 -206
- package/src/components/index.ts +0 -29
- package/src/env.ts +0 -20
- package/src/lib/i18n.tsx +0 -353
- package/src/lib/validation.ts +0 -250
- package/src/pages/api/globals/[slug]/test.ts +0 -171
package/README.md
CHANGED
|
@@ -1,272 +1,46 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
The admin dashboard is designed to work alongside a Kyro CMS project. In your Astro project:
|
|
49
|
-
|
|
50
|
-
1. Install the admin package:
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
npm install @kyro-cms/admin
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
2. Create an admin page at `src/pages/admin/index.astro`:
|
|
57
|
-
|
|
58
|
-
```astro
|
|
59
|
-
---
|
|
60
|
-
import { Admin } from '@kyro-cms/admin';
|
|
61
|
-
import config from '../../../kyro.config';
|
|
62
|
-
---
|
|
63
|
-
<!DOCTYPE html>
|
|
64
|
-
<html lang="en">
|
|
65
|
-
<head>
|
|
66
|
-
<meta charset="UTF-8" />
|
|
67
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
68
|
-
<title>Admin - My Kyro CMS</title>
|
|
69
|
-
</head>
|
|
70
|
-
<body>
|
|
71
|
-
<Admin client:load config={config} />
|
|
72
|
-
</body>
|
|
73
|
-
</html>
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
3. Configure your `astro.config.mjs` with the Node adapter:
|
|
77
|
-
|
|
78
|
-
```js
|
|
79
|
-
import { defineConfig } from "astro/config";
|
|
80
|
-
import node from "@astrojs/node";
|
|
81
|
-
|
|
82
|
-
export default defineConfig({
|
|
83
|
-
output: "server",
|
|
84
|
-
adapter: node({ mode: "standalone" }),
|
|
85
|
-
});
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
## Database Configuration
|
|
89
|
-
|
|
90
|
-
The admin supports multiple database backends. The auth system automatically uses the same database as your content data.
|
|
91
|
-
|
|
92
|
-
### SQLite (Default)
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
# No configuration needed - uses ./data/kyro.db
|
|
96
|
-
DB_TYPE=sqlite
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### PostgreSQL
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
DB_TYPE=postgres
|
|
103
|
-
DB_HOST=localhost
|
|
104
|
-
DB_PORT=5432
|
|
105
|
-
DB_NAME=kyro
|
|
106
|
-
DB_USER=admin
|
|
107
|
-
DB_PASSWORD=secret
|
|
108
|
-
# Or use connection string
|
|
109
|
-
DB_CONNECTION_STRING=postgresql://admin:secret@localhost:5432/kyro
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### MySQL
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
DB_TYPE=mysql
|
|
116
|
-
DB_HOST=localhost
|
|
117
|
-
DB_PORT=3306
|
|
118
|
-
DB_NAME=kyro
|
|
119
|
-
DB_USER=root
|
|
120
|
-
DB_PASSWORD=secret
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
### MongoDB
|
|
124
|
-
|
|
125
|
-
```bash
|
|
126
|
-
DB_TYPE=mongodb
|
|
127
|
-
DB_HOST=localhost
|
|
128
|
-
DB_PORT=27017
|
|
129
|
-
DB_NAME=kyro
|
|
130
|
-
# Or use connection string
|
|
131
|
-
DB_CONNECTION_STRING=mongodb://admin:secret@localhost:27017/kyro
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### Connection Pooling
|
|
135
|
-
|
|
136
|
-
For PostgreSQL and MySQL, configure connection pool:
|
|
137
|
-
|
|
138
|
-
```bash
|
|
139
|
-
DB_POOL_MIN=5
|
|
140
|
-
DB_POOL_MAX=20
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
### Environment Variables
|
|
144
|
-
|
|
145
|
-
| Variable | Description | Default |
|
|
146
|
-
| ------------------------- | --------------------------------------------- | ------------------------- |
|
|
147
|
-
| `DB_TYPE` | Database type (sqlite/postgres/mysql/mongodb) | `sqlite` |
|
|
148
|
-
| `DB_CONNECTION_STRING` | Full connection URI | - |
|
|
149
|
-
| `DB_HOST` | Database host | `localhost` |
|
|
150
|
-
| `DB_PORT` | Database port | 5432/3306/27017 |
|
|
151
|
-
| `DB_NAME` | Database name | `kyro` |
|
|
152
|
-
| `DB_USER` | Database username | - |
|
|
153
|
-
| `DB_PASSWORD` | Database password | - |
|
|
154
|
-
| `DB_POOL_MIN` | Minimum pool connections | 5 |
|
|
155
|
-
| `DB_POOL_MAX` | Maximum pool connections | 20 |
|
|
156
|
-
| `JWT_SECRET` | Secret for signing JWT tokens | `change-me-in-production` |
|
|
157
|
-
| `JWT_EXPIRES_IN` | Token expiration time | `24h` |
|
|
158
|
-
| `KYRO_ALLOW_REGISTRATION` | Allow public registration | `true` |
|
|
159
|
-
|
|
160
|
-
## Authentication
|
|
161
|
-
|
|
162
|
-
Auth is automatically handled by the adapter matching your `DB_TYPE`:
|
|
163
|
-
|
|
164
|
-
- **SQLite** → Uses SQLiteAuthAdapter (local file-based)
|
|
165
|
-
- **Postgres** → Uses PostgresAuthAdapter (same DB)
|
|
166
|
-
- **MongoDB** → Uses MongoDBAuthAdapter (same DB)
|
|
167
|
-
|
|
168
|
-
### Creating Your First Admin User
|
|
169
|
-
|
|
170
|
-
1. Start the dev server: `npm run dev`
|
|
171
|
-
2. Visit `http://localhost:4321/admin`
|
|
172
|
-
3. Register with your email and password
|
|
173
|
-
4. The first user automatically gets the `super_admin` role
|
|
174
|
-
|
|
175
|
-
### Auth API Endpoints
|
|
176
|
-
|
|
177
|
-
| Endpoint | Method | Description |
|
|
178
|
-
| -------------------- | ------ | ---------------------------- |
|
|
179
|
-
| `/api/auth/login` | POST | Authenticate user |
|
|
180
|
-
| `/api/auth/register` | POST | Register new user |
|
|
181
|
-
| `/api/auth/logout` | POST | Invalidate session |
|
|
182
|
-
| `/api/auth/me` | GET | Get current user info |
|
|
183
|
-
| `/api/auth/users` | GET | List all users (admin only) |
|
|
184
|
-
| `/api/auth/users` | POST | Create new user (admin only) |
|
|
185
|
-
|
|
186
|
-
## Project Structure
|
|
187
|
-
|
|
188
|
-
```
|
|
189
|
-
admin/
|
|
190
|
-
├── src/
|
|
191
|
-
│ ├── lib/
|
|
192
|
-
│ │ ├── db/ # Database adapters
|
|
193
|
-
│ │ │ ├── adapter.ts # DatabaseAdapter interface
|
|
194
|
-
│ │ │ ├── index.ts # Factory & config
|
|
195
|
-
│ │ │ ├── sqlite-adapter.ts
|
|
196
|
-
│ │ │ ├── postgres-adapter.ts
|
|
197
|
-
│ │ │ ├── mysql-adapter.ts
|
|
198
|
-
│ │ │ ├── mongodb-adapter.ts
|
|
199
|
-
│ │ │ └── mongodb-auth-adapter.ts
|
|
200
|
-
│ │ ├── auth/ # Auth adapter
|
|
201
|
-
│ │ │ └── sqlite-adapter.ts
|
|
202
|
-
│ │ ├── dataStore.ts # Content data wrapper
|
|
203
|
-
│ │ └── config.ts # Collection/global config
|
|
204
|
-
│ ├── components/ # React UI components
|
|
205
|
-
│ ├── pages/ # Astro pages + API routes
|
|
206
|
-
│ │ └── api/ # REST API endpoints
|
|
207
|
-
│ │ └── auth/ # Authentication endpoints
|
|
208
|
-
│ └── collections/ # Auth collection config
|
|
209
|
-
├── test/ # Vitest tests
|
|
210
|
-
├── public/ # Static assets
|
|
211
|
-
└── astro.config.mjs # Astro configuration
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
## Adapter Architecture
|
|
215
|
-
|
|
216
|
-
```
|
|
217
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
218
|
-
│ Application Code │
|
|
219
|
-
└─────────────────────────┬───────────────────────────────────┘
|
|
220
|
-
│
|
|
221
|
-
▼
|
|
222
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
223
|
-
│ dataStore.ts (Wrapper) │
|
|
224
|
-
└─────────────────────────┬───────────────────────────────────┘
|
|
225
|
-
│
|
|
226
|
-
▼
|
|
227
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
228
|
-
│ db/index.ts (Factory/Registry) │
|
|
229
|
-
│ - Reads DB_TYPE from env │
|
|
230
|
-
│ - Lazy-loads appropriate adapter │
|
|
231
|
-
│ - Auto-selects matching auth adapter │
|
|
232
|
-
└─────────────────────────┬───────────────────────────────────┘
|
|
233
|
-
│
|
|
234
|
-
▼
|
|
235
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
236
|
-
│ adapter.ts (DatabaseAdapter Interface) │
|
|
237
|
-
│ - find, findById, create, update, delete │
|
|
238
|
-
│ - findGlobal, updateGlobal, seedGlobal │
|
|
239
|
-
│ - seed, isSeeded, count │
|
|
240
|
-
└─────────────────────────┬───────────────────────────────────┘
|
|
241
|
-
│
|
|
242
|
-
┌─────────────────────┼─────────────────────┐
|
|
243
|
-
▼ ▼ ▼
|
|
244
|
-
┌─────────┐ ┌─────────┐ ┌─────────┐
|
|
245
|
-
│ SQLite │ │ PG │ │ MySQL │
|
|
246
|
-
└─────────┘ └─────────┘ └─────────┘
|
|
247
|
-
┌─────────┐
|
|
248
|
-
│ MongoDB │
|
|
249
|
-
└─────────┘
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
## Security
|
|
253
|
-
|
|
254
|
-
- **Password Hashing** — bcryptjs with 10 salt rounds
|
|
255
|
-
- **JWT Tokens** — Signed tokens with configurable expiration
|
|
256
|
-
- **Session Management** — Server-side session tracking via DB adapter
|
|
257
|
-
- **Middleware Protection** — All non-auth routes require valid JWT
|
|
258
|
-
- **Password Policy** — Minimum 8 characters
|
|
259
|
-
|
|
260
|
-
## Testing
|
|
261
|
-
|
|
262
|
-
```bash
|
|
263
|
-
# Run all tests
|
|
264
|
-
npm run test:run
|
|
265
|
-
|
|
266
|
-
# Run tests with watch mode
|
|
267
|
-
npm run test
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
## License
|
|
271
|
-
|
|
272
|
-
MIT
|
|
1
|
+
# Kyro Admin MVP Extensibility
|
|
2
|
+
|
|
3
|
+
This repository contains a minimal, upgrade-safe extension surface for @kyro-cms/admin.
|
|
4
|
+
|
|
5
|
+
- Exposed modules: theme, hooks, plugins, blocks, fields, and the Astro integration (kyroAdmin).
|
|
6
|
+
- MVP samples are included for quick experimentation:
|
|
7
|
+
- MVP Hook samples: sampleHook, sampleHook2
|
|
8
|
+
- MVP Block samples: sampleBlock, sampleBlock2
|
|
9
|
+
- MVP Field samples: sampleField, sampleField2
|
|
10
|
+
- MVP Plugin sample: samplePlugin
|
|
11
|
+
|
|
12
|
+
How to try locally
|
|
13
|
+
|
|
14
|
+
- Build:
|
|
15
|
+
- Run: bun install
|
|
16
|
+
- Run: bun run build
|
|
17
|
+
- Dist artifacts will be emitted in admin/dist
|
|
18
|
+
- Test:
|
|
19
|
+
- Run: bun run test
|
|
20
|
+
- Tests cover MVP hooks; additional MVP tests can be added later
|
|
21
|
+
|
|
22
|
+
Usage notes
|
|
23
|
+
|
|
24
|
+
- kyroAdmin() is available from @kyro-cms/admin/integration and can be wired into Astro config
|
|
25
|
+
- MVP samples are designed to be initial templates for plugin authors; replace or extend with real logic
|
|
26
|
+
|
|
27
|
+
Next steps
|
|
28
|
+
|
|
29
|
+
- Decide on final API shape (per-feature exports vs central namespace)
|
|
30
|
+
- Add more MVP samples for blocks/fields/plugins and write accompanying tests
|
|
31
|
+
- Add documentation explaining how to author plugins and blocks
|
|
32
|
+
- MVP samples are included for quick experimentation:
|
|
33
|
+
- MVP Hook samples: sampleHook, sampleHook2
|
|
34
|
+
- MVP Block samples: sampleBlock, sampleBlock2
|
|
35
|
+
- MVP Field samples: sampleField, sampleField2
|
|
36
|
+
- MVP Plugin sample: samplePlugin
|
|
37
|
+
|
|
38
|
+
Export surface (MVP)
|
|
39
|
+
|
|
40
|
+
- The MVP exports are available per module:
|
|
41
|
+
- @kyro-cms/admin/hooks -> sampleHook, sampleHook2 (and lifecycle/data hooks)
|
|
42
|
+
- @kyro-cms/admin/blocks -> sampleBlock, sampleBlock2 (and registry)
|
|
43
|
+
- @kyro-cms/admin/fields -> sampleField, sampleField2 (and registry)
|
|
44
|
+
- @kyro-cms/admin/plugins -> samplePlugin, samplePlugin2 (and registry)
|
|
45
|
+
- The main integration (kyroAdmin) is available via @kyro-cms/admin/integration
|
|
46
|
+
- The theme module is available via @kyro-cms/admin/theme
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kyro-cms/admin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -14,6 +14,30 @@
|
|
|
14
14
|
".": {
|
|
15
15
|
"import": "./src/index.ts",
|
|
16
16
|
"default": "./src/index.ts"
|
|
17
|
+
},
|
|
18
|
+
"./theme": {
|
|
19
|
+
"import": "./src/theme/index.ts",
|
|
20
|
+
"default": "./src/theme/index.ts"
|
|
21
|
+
},
|
|
22
|
+
"./hooks": {
|
|
23
|
+
"import": "./src/hooks/index.ts",
|
|
24
|
+
"default": "./src/hooks/index.ts"
|
|
25
|
+
},
|
|
26
|
+
"./plugins": {
|
|
27
|
+
"import": "./src/plugins/index.ts",
|
|
28
|
+
"default": "./src/plugins/index.ts"
|
|
29
|
+
},
|
|
30
|
+
"./blocks": {
|
|
31
|
+
"import": "./src/blocks/index.ts",
|
|
32
|
+
"default": "./src/blocks/index.ts"
|
|
33
|
+
},
|
|
34
|
+
"./fields": {
|
|
35
|
+
"import": "./src/fields/index.ts",
|
|
36
|
+
"default": "./src/fields/index.ts"
|
|
37
|
+
},
|
|
38
|
+
"./integration": {
|
|
39
|
+
"import": "./src/integration.ts",
|
|
40
|
+
"default": "./src/integration.ts"
|
|
17
41
|
}
|
|
18
42
|
},
|
|
19
43
|
"files": [
|
|
@@ -21,7 +45,7 @@
|
|
|
21
45
|
],
|
|
22
46
|
"scripts": {
|
|
23
47
|
"dev": "astro dev",
|
|
24
|
-
"build": "
|
|
48
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
25
49
|
"preview": "astro preview",
|
|
26
50
|
"check": "astro check",
|
|
27
51
|
"test": "vitest",
|
|
@@ -47,8 +71,7 @@
|
|
|
47
71
|
"@dnd-kit/sortable": "^10.0.0",
|
|
48
72
|
"@dnd-kit/utilities": "^3.2.2",
|
|
49
73
|
"@graphiql/react": "^0.37.3",
|
|
50
|
-
"@kyro-cms/core": "^0.
|
|
51
|
-
"@kyro-cms/utils": "^0.2.9",
|
|
74
|
+
"@kyro-cms/core": "^0.3.0",
|
|
52
75
|
"@portabletext/editor": "^6.6.3",
|
|
53
76
|
"@portabletext/react": "^6.0.3",
|
|
54
77
|
"@portabletext/schema": "^2.1.1",
|
|
@@ -80,16 +103,18 @@
|
|
|
80
103
|
"zustand": "^5.0.3"
|
|
81
104
|
},
|
|
82
105
|
"devDependencies": {
|
|
106
|
+
"@astrojs/check": "^0.9.9",
|
|
83
107
|
"@types/react": "^19.0.0",
|
|
84
108
|
"@types/react-dom": "^19.0.0",
|
|
85
109
|
"dotenv": "^17.4.2",
|
|
86
110
|
"dotenv-cli": "^11.0.0",
|
|
87
111
|
"drizzle-kit": "^0.31.10",
|
|
88
|
-
"typescript": "^
|
|
89
|
-
"vitest": "^4.1.4"
|
|
112
|
+
"typescript": "^6.0.3",
|
|
113
|
+
"vitest": "^4.1.4",
|
|
114
|
+
"tsup": "^6.0.0"
|
|
90
115
|
},
|
|
91
116
|
"peerDependencies": {
|
|
92
|
-
"@kyro-cms/core": "^0.
|
|
117
|
+
"@kyro-cms/core": "^0.3.0"
|
|
93
118
|
},
|
|
94
119
|
"repository": {
|
|
95
120
|
"type": "git",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { KyroBlock } from "../types.ts";
|
|
3
|
+
import { registerBlock } from "../registry.ts";
|
|
4
|
+
|
|
5
|
+
const SampleBlock2Renderer: KyroBlock["render"] = (props) => {
|
|
6
|
+
const { data } = props;
|
|
7
|
+
return (
|
|
8
|
+
<div style={{ border: "2px dashed #888", padding: 10, borderRadius: 8 }}>
|
|
9
|
+
<strong>Sample Block 2</strong>
|
|
10
|
+
<pre style={{ marginTop: 6 }}>{JSON.stringify(data ?? {}, null, 2)}</pre>
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const block: KyroBlock = {
|
|
16
|
+
id: "sample-block-2",
|
|
17
|
+
label: "Sample Block 2",
|
|
18
|
+
category: "demo",
|
|
19
|
+
schema: [
|
|
20
|
+
{ name: "subtitle", label: "Subtitle", type: "text", required: false },
|
|
21
|
+
],
|
|
22
|
+
render: SampleBlock2Renderer,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
registerBlock(block);
|
|
26
|
+
export default block;
|
|
27
|
+
export { block as sampleBlock2 };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { KyroBlock } from "../types.ts";
|
|
3
|
+
import { registerBlock } from "../registry.ts";
|
|
4
|
+
|
|
5
|
+
const SampleBlockRenderer: KyroBlock["render"] = (props) => {
|
|
6
|
+
const { data } = props;
|
|
7
|
+
return (
|
|
8
|
+
<div style={{ border: "1px solid #ccc", padding: 8, borderRadius: 6 }}>
|
|
9
|
+
<strong>Sample Block</strong>
|
|
10
|
+
<pre style={{ marginTop: 6 }}>{JSON.stringify(data ?? {}, null, 2)}</pre>
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const block: KyroBlock = {
|
|
16
|
+
id: "sample-block",
|
|
17
|
+
label: "Sample Block",
|
|
18
|
+
category: "demo",
|
|
19
|
+
schema: [{ name: "title", label: "Title", type: "text", required: true }],
|
|
20
|
+
render: SampleBlockRenderer,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
registerBlock(block);
|
|
24
|
+
|
|
25
|
+
export default block;
|
|
26
|
+
export { block as sampleBlock };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
registerBlock,
|
|
3
|
+
unregisterBlock,
|
|
4
|
+
getBlock,
|
|
5
|
+
getBlocks,
|
|
6
|
+
getBlocksByCategory,
|
|
7
|
+
useBlockRenderer,
|
|
8
|
+
} from "./registry.ts";
|
|
9
|
+
export type { KyroBlock, BlockRenderProps } from "./types.ts";
|
|
10
|
+
export { default as sampleBlock } from "./examples/sample-block";
|
|
11
|
+
export { default as sampleBlock2 } from "./examples/sample-block-2.tsx";
|
|
12
|
+
|
|
13
|
+
// Re-export core block types for type-safe block registration
|
|
14
|
+
export type { Block as CoreBlock, RichTextBlock } from "@kyro-cms/core";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { KyroBlock } from "./types.ts";
|
|
2
|
+
|
|
3
|
+
const blocks: Map<string, KyroBlock> = new Map();
|
|
4
|
+
|
|
5
|
+
export function registerBlock(block: KyroBlock): void {
|
|
6
|
+
if (!block.id || typeof block.id !== "string") {
|
|
7
|
+
throw new Error("Block must have a valid id");
|
|
8
|
+
}
|
|
9
|
+
if (!block.label) {
|
|
10
|
+
throw new Error("Block must have a label");
|
|
11
|
+
}
|
|
12
|
+
blocks.set(block.id, block);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function unregisterBlock(id: string): void {
|
|
16
|
+
blocks.delete(id);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getBlock(id: string): KyroBlock | undefined {
|
|
20
|
+
return blocks.get(id);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getBlocks(): KyroBlock[] {
|
|
24
|
+
return Array.from(blocks.values());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getBlocksByCategory(category: string): KyroBlock[] {
|
|
28
|
+
return Array.from(blocks.values()).filter((b) => b.category === category);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function useBlockRenderer(id: string) {
|
|
32
|
+
const block = blocks.get(id);
|
|
33
|
+
if (!block) {
|
|
34
|
+
console.warn(`Block "${id}" not found in registry`);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return block.render;
|
|
38
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { KyroTheme } from "../theme/tokens.ts";
|
|
3
|
+
import type { Block as CoreBlock, Field } from "@kyro-cms/core";
|
|
4
|
+
|
|
5
|
+
export interface BlockRenderProps {
|
|
6
|
+
data: Record<string, unknown>;
|
|
7
|
+
context: {
|
|
8
|
+
theme: KyroTheme;
|
|
9
|
+
locale: string;
|
|
10
|
+
isPreview: boolean;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface KyroBlock extends Omit<CoreBlock, "fields" | "slug"> {
|
|
15
|
+
id: string;
|
|
16
|
+
label: string;
|
|
17
|
+
category?: string;
|
|
18
|
+
icon?: ReactNode;
|
|
19
|
+
schema: Field[];
|
|
20
|
+
render: (props: BlockRenderProps) => ReactNode;
|
|
21
|
+
preview?: (props: BlockRenderProps) => ReactNode;
|
|
22
|
+
settings?: Record<string, unknown>;
|
|
23
|
+
}
|
package/src/components/Admin.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect } from "react";
|
|
2
|
-
import { apiPost } from "
|
|
2
|
+
import { apiPost } from "../lib/api";
|
|
3
3
|
import type { CollectionConfig, GlobalConfig } from "@kyro-cms/core/client";
|
|
4
4
|
import { ListView } from "./ListView";
|
|
5
5
|
import { DetailView } from "./DetailView";
|
|
@@ -17,7 +17,7 @@ import { slugifyText } from "../lib/slugify";
|
|
|
17
17
|
|
|
18
18
|
import { BlocksField } from "./fields/BlocksField";
|
|
19
19
|
import PortableTextField from "./fields/PortableTextField";
|
|
20
|
-
import { ConfirmModal, UIModal } from "./Modal";
|
|
20
|
+
import { ConfirmModal, Modal as UIModal } from "./ui/Modal";
|
|
21
21
|
|
|
22
22
|
interface AutoFormProps {
|
|
23
23
|
config: CollectionConfig | GlobalConfig;
|
|
@@ -31,7 +31,7 @@ interface AutoFormProps {
|
|
|
31
31
|
layout?: "split" | "single";
|
|
32
32
|
onActionSuccess?: (message: string) => void;
|
|
33
33
|
onActionError?: (message: string) => void;
|
|
34
|
-
documentStatus?: "draft" | "published";
|
|
34
|
+
documentStatus?: "draft" | "published" | "scheduled" | "archived";
|
|
35
35
|
justSaved?: boolean;
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
|
-
import { apiPost } from "
|
|
2
|
+
import { apiPost } from "../lib/api";
|
|
3
3
|
import type { KyroConfig, CollectionConfig } from "@kyro-cms/core/client";
|
|
4
4
|
import { AutoForm } from "./AutoForm";
|
|
5
5
|
import { Spinner } from "./ui/Spinner";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect, useMemo, useCallback } from "react";
|
|
2
2
|
import { Spinner } from "./ui/Spinner";
|
|
3
3
|
import { ConfirmModal } from "./ui/Modal";
|
|
4
|
-
import { apiGet, apiDelete, withCacheBust } from "
|
|
4
|
+
import { apiGet, apiDelete, withCacheBust } from "../lib/api";
|
|
5
5
|
|
|
6
6
|
export interface FieldConfig {
|
|
7
7
|
name: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect } from "react";
|
|
2
|
-
import { apiGet, apiDelete } from "
|
|
2
|
+
import { apiGet, apiDelete } from "../lib/api";
|
|
3
3
|
import type { CollectionConfig, KyroConfig } from "@kyro-cms/core/client";
|
|
4
4
|
import { Spinner } from "./ui/Spinner";
|
|
5
5
|
import { ConfirmModal } from "./ui/Modal";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect } from "react";
|
|
2
|
-
import { apiGet, apiPost } from "
|
|
2
|
+
import { apiGet, apiPost } from "../lib/api";
|
|
3
3
|
import { ThemeProvider, type ThemeMode } from "./ThemeProvider";
|
|
4
4
|
import { Toast, ToastProvider } from "./ui/Toast";
|
|
5
5
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
|
-
import { apiGet, apiPost, apiPatch, apiDelete } from "
|
|
3
|
-
import { formatDate } from "
|
|
2
|
+
import { apiGet, apiPost, apiPatch, apiDelete } from "../lib/api";
|
|
3
|
+
import { formatDate } from "../lib/date-utils";
|
|
4
4
|
import {
|
|
5
5
|
Webhook,
|
|
6
6
|
Plus,
|