create-momentum-app 0.0.1
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/LICENSE +21 -0
- package/README.md +33 -0
- package/index.cjs +222 -0
- package/package.json +69 -0
- package/templates/analog/eslint.config.mjs +9 -0
- package/templates/analog/index.html +25 -0
- package/templates/analog/package.json.tmpl +47 -0
- package/templates/analog/postcss.config.cjs +6 -0
- package/templates/analog/src/app/app.config.server.ts +7 -0
- package/templates/analog/src/app/app.config.ts +27 -0
- package/templates/analog/src/app/app.ts +9 -0
- package/templates/analog/src/collections/posts.ts +16 -0
- package/templates/analog/src/main.server.ts +6 -0
- package/templates/analog/src/main.ts +5 -0
- package/templates/analog/src/momentum.config.ts.tmpl +50 -0
- package/templates/analog/src/server/middleware/00-init.ts +13 -0
- package/templates/analog/src/server/routes/api/[...momentum].ts +73 -0
- package/templates/analog/src/server/utils/momentum-init.ts.tmpl +87 -0
- package/templates/analog/src/styles.css +140 -0
- package/templates/analog/tailwind.config.ts +12 -0
- package/templates/analog/tsconfig.app.json +9 -0
- package/templates/analog/tsconfig.json +26 -0
- package/templates/analog/vite.config.ts.tmpl +19 -0
- package/templates/angular/angular.json.tmpl +71 -0
- package/templates/angular/eslint.config.mjs +31 -0
- package/templates/angular/package.json.tmpl +47 -0
- package/templates/angular/postcss.config.js +6 -0
- package/templates/angular/src/app/app.config.server.ts +7 -0
- package/templates/angular/src/app/app.config.ts +14 -0
- package/templates/angular/src/app/app.routes.ts +3 -0
- package/templates/angular/src/app/app.ts +9 -0
- package/templates/angular/src/collections/posts.ts +16 -0
- package/templates/angular/src/index.html.tmpl +13 -0
- package/templates/angular/src/main.server.ts +7 -0
- package/templates/angular/src/main.ts +5 -0
- package/templates/angular/src/momentum.config.ts.tmpl +59 -0
- package/templates/angular/src/server.ts.tmpl +74 -0
- package/templates/angular/src/styles.css +140 -0
- package/templates/angular/tailwind.config.js +11 -0
- package/templates/angular/tsconfig.app.json +9 -0
- package/templates/angular/tsconfig.json +26 -0
- package/templates/shared/.claude/CLAUDE.md +155 -0
- package/templates/shared/.claude/skills/collection/SKILL.md +117 -0
- package/templates/shared/.claude/skills/momentum-api/SKILL.md +158 -0
- package/templates/shared/.env.example.tmpl +11 -0
- package/templates/shared/README.md.tmpl +27 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@layer base {
|
|
6
|
+
:root {
|
|
7
|
+
/* Base colors */
|
|
8
|
+
--mcms-background: 0 0% 100%;
|
|
9
|
+
--mcms-foreground: 222 47% 11%;
|
|
10
|
+
|
|
11
|
+
/* Card */
|
|
12
|
+
--mcms-card: 0 0% 100%;
|
|
13
|
+
--mcms-card-foreground: 222 47% 11%;
|
|
14
|
+
|
|
15
|
+
/* Primary - Blue */
|
|
16
|
+
--mcms-primary: 221 83% 53%;
|
|
17
|
+
--mcms-primary-foreground: 210 40% 98%;
|
|
18
|
+
|
|
19
|
+
/* Secondary - Gray */
|
|
20
|
+
--mcms-secondary: 210 40% 96%;
|
|
21
|
+
--mcms-secondary-foreground: 222 47% 11%;
|
|
22
|
+
|
|
23
|
+
/* Muted - Light gray for subtle backgrounds */
|
|
24
|
+
--mcms-muted: 210 40% 96%;
|
|
25
|
+
--mcms-muted-foreground: 215 16% 43%;
|
|
26
|
+
|
|
27
|
+
/* Accent - Hover states */
|
|
28
|
+
--mcms-accent: 210 40% 96%;
|
|
29
|
+
--mcms-accent-foreground: 222 47% 11%;
|
|
30
|
+
|
|
31
|
+
/* Destructive - Red for dangerous actions */
|
|
32
|
+
--mcms-destructive: 0 84% 60%;
|
|
33
|
+
--mcms-destructive-foreground: 210 40% 98%;
|
|
34
|
+
|
|
35
|
+
/* Success - Green for positive feedback */
|
|
36
|
+
--mcms-success: 142 76% 36%;
|
|
37
|
+
--mcms-success-foreground: 0 0% 100%;
|
|
38
|
+
|
|
39
|
+
/* Warning - Amber for caution */
|
|
40
|
+
--mcms-warning: 38 92% 50%;
|
|
41
|
+
--mcms-warning-foreground: 48 96% 5%;
|
|
42
|
+
|
|
43
|
+
/* Info - Blue for informational messages */
|
|
44
|
+
--mcms-info: 199 89% 48%;
|
|
45
|
+
--mcms-info-foreground: 0 0% 100%;
|
|
46
|
+
|
|
47
|
+
/* Overlay - For modal backdrops */
|
|
48
|
+
--mcms-overlay: 0 0% 0%;
|
|
49
|
+
|
|
50
|
+
/* Border, input, and ring */
|
|
51
|
+
--mcms-border: 214 32% 91%;
|
|
52
|
+
--mcms-input: 214 32% 91%;
|
|
53
|
+
--mcms-ring: 221 83% 53%;
|
|
54
|
+
|
|
55
|
+
/* Border radius */
|
|
56
|
+
--mcms-radius: 0.5rem;
|
|
57
|
+
|
|
58
|
+
/* Sidebar colors - Dark sidebar in light mode */
|
|
59
|
+
--mcms-sidebar: 222 47% 11%;
|
|
60
|
+
--mcms-sidebar-foreground: 210 40% 98%;
|
|
61
|
+
--mcms-sidebar-primary: 221 83% 53%;
|
|
62
|
+
--mcms-sidebar-primary-foreground: 210 40% 98%;
|
|
63
|
+
--mcms-sidebar-accent: 217 33% 17%;
|
|
64
|
+
--mcms-sidebar-accent-foreground: 210 40% 98%;
|
|
65
|
+
--mcms-sidebar-border: 217 33% 20%;
|
|
66
|
+
--mcms-sidebar-ring: 221 83% 53%;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.dark {
|
|
70
|
+
/* Base colors - Dark background */
|
|
71
|
+
--mcms-background: 222 47% 11%;
|
|
72
|
+
--mcms-foreground: 210 40% 98%;
|
|
73
|
+
|
|
74
|
+
/* Card - slightly lighter than background */
|
|
75
|
+
--mcms-card: 217 33% 15%;
|
|
76
|
+
--mcms-card-foreground: 210 40% 98%;
|
|
77
|
+
|
|
78
|
+
/* Primary - Blue */
|
|
79
|
+
--mcms-primary: 217 91% 60%;
|
|
80
|
+
--mcms-primary-foreground: 222 47% 11%;
|
|
81
|
+
|
|
82
|
+
/* Secondary - distinct from card for badges */
|
|
83
|
+
--mcms-secondary: 215 28% 25%;
|
|
84
|
+
--mcms-secondary-foreground: 210 40% 98%;
|
|
85
|
+
|
|
86
|
+
/* Muted - subtle background */
|
|
87
|
+
--mcms-muted: 217 33% 20%;
|
|
88
|
+
--mcms-muted-foreground: 215 20% 65%;
|
|
89
|
+
|
|
90
|
+
/* Accent - hover states */
|
|
91
|
+
--mcms-accent: 217 33% 22%;
|
|
92
|
+
--mcms-accent-foreground: 210 40% 98%;
|
|
93
|
+
|
|
94
|
+
/* Destructive */
|
|
95
|
+
--mcms-destructive: 0 62% 50%;
|
|
96
|
+
--mcms-destructive-foreground: 210 40% 98%;
|
|
97
|
+
|
|
98
|
+
/* Success */
|
|
99
|
+
--mcms-success: 142 71% 45%;
|
|
100
|
+
--mcms-success-foreground: 0 0% 100%;
|
|
101
|
+
|
|
102
|
+
/* Warning */
|
|
103
|
+
--mcms-warning: 38 92% 50%;
|
|
104
|
+
--mcms-warning-foreground: 48 96% 5%;
|
|
105
|
+
|
|
106
|
+
/* Info */
|
|
107
|
+
--mcms-info: 199 89% 48%;
|
|
108
|
+
--mcms-info-foreground: 0 0% 100%;
|
|
109
|
+
|
|
110
|
+
/* Overlay */
|
|
111
|
+
--mcms-overlay: 0 0% 0%;
|
|
112
|
+
|
|
113
|
+
/* Border, input, and ring - slightly visible */
|
|
114
|
+
--mcms-border: 217 33% 22%;
|
|
115
|
+
--mcms-input: 217 33% 22%;
|
|
116
|
+
--mcms-ring: 224 76% 48%;
|
|
117
|
+
|
|
118
|
+
/* Sidebar colors - Slightly lighter than main in dark mode */
|
|
119
|
+
--mcms-sidebar: 224 71% 4%;
|
|
120
|
+
--mcms-sidebar-foreground: 213 31% 91%;
|
|
121
|
+
--mcms-sidebar-primary: 217 91% 60%;
|
|
122
|
+
--mcms-sidebar-primary-foreground: 210 40% 98%;
|
|
123
|
+
--mcms-sidebar-accent: 217 33% 17%;
|
|
124
|
+
--mcms-sidebar-accent-foreground: 213 31% 91%;
|
|
125
|
+
--mcms-sidebar-border: 217 33% 12%;
|
|
126
|
+
--mcms-sidebar-ring: 217 91% 60%;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* Apply base styles */
|
|
131
|
+
@layer base {
|
|
132
|
+
* {
|
|
133
|
+
border-color: hsl(var(--mcms-border));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
body {
|
|
137
|
+
background-color: hsl(var(--mcms-background));
|
|
138
|
+
color: hsl(var(--mcms-foreground));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const adminPreset = require('@momentumcms/admin/tailwind.preset');
|
|
2
|
+
|
|
3
|
+
/** @type {import('tailwindcss').Config} */
|
|
4
|
+
module.exports = {
|
|
5
|
+
presets: [adminPreset],
|
|
6
|
+
content: [
|
|
7
|
+
'./src/**/*.{html,ts}',
|
|
8
|
+
'./node_modules/@momentumcms/admin/**/*.{html,ts,mjs}',
|
|
9
|
+
'./node_modules/@momentumcms/ui/**/*.{html,ts,mjs}',
|
|
10
|
+
],
|
|
11
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compileOnSave": false,
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./dist/out-tsc",
|
|
5
|
+
"strict": true,
|
|
6
|
+
"noImplicitOverride": true,
|
|
7
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
8
|
+
"noImplicitReturns": true,
|
|
9
|
+
"noFallthroughCasesInSwitch": true,
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"declaration": false,
|
|
12
|
+
"experimentalDecorators": true,
|
|
13
|
+
"moduleResolution": "bundler",
|
|
14
|
+
"importHelpers": true,
|
|
15
|
+
"target": "ES2022",
|
|
16
|
+
"module": "ES2022",
|
|
17
|
+
"lib": ["ES2022", "dom"],
|
|
18
|
+
"skipLibCheck": true
|
|
19
|
+
},
|
|
20
|
+
"angularCompilerOptions": {
|
|
21
|
+
"enableI18nLegacyMessageIdFormat": false,
|
|
22
|
+
"strictInjectionParameters": true,
|
|
23
|
+
"strictInputAccessModifiers": true,
|
|
24
|
+
"strictTemplates": true
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Momentum CMS App
|
|
2
|
+
|
|
3
|
+
Angular-based headless CMS application built with [Momentum CMS](https://github.com/momentum-cms/momentum-cms).
|
|
4
|
+
|
|
5
|
+
## Tech Stack
|
|
6
|
+
|
|
7
|
+
- Angular 21 (SSR) with Express or Analog.js (Nitro)
|
|
8
|
+
- Drizzle ORM (PostgreSQL or SQLite)
|
|
9
|
+
- Better Auth for authentication
|
|
10
|
+
- Tailwind CSS for styling
|
|
11
|
+
|
|
12
|
+
## Commands
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm run dev # Start dev server
|
|
16
|
+
npm run build # Production build
|
|
17
|
+
npm start # Start production server
|
|
18
|
+
npx drizzle-kit generate # Create SQL migrations from schema changes
|
|
19
|
+
npx drizzle-kit push # Direct push (dev only)
|
|
20
|
+
npx drizzle-kit migrate # Apply migrations (production)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Project Structure
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
src/
|
|
27
|
+
collections/ # Collection definitions (data models)
|
|
28
|
+
momentum.config.ts # Central configuration (db, auth, collections, plugins)
|
|
29
|
+
server.ts # Server entry point (Express) or server/ (Analog)
|
|
30
|
+
app/ # Angular app components and routes
|
|
31
|
+
styles.css # Tailwind + Momentum theme variables
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Defining Collections
|
|
35
|
+
|
|
36
|
+
Collections are the core data model. Each collection generates a database table, REST API, and admin UI.
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { defineCollection, text, richText, select, relationship } from '@momentumcms/core';
|
|
40
|
+
|
|
41
|
+
export const Posts = defineCollection({
|
|
42
|
+
slug: 'posts',
|
|
43
|
+
admin: { useAsTitle: 'title', group: 'Content' },
|
|
44
|
+
access: {
|
|
45
|
+
read: () => true,
|
|
46
|
+
create: ({ req }) => !!req.user,
|
|
47
|
+
update: ({ req }) => req.user?.role === 'admin',
|
|
48
|
+
delete: ({ req }) => req.user?.role === 'admin',
|
|
49
|
+
},
|
|
50
|
+
fields: [
|
|
51
|
+
text('title', { required: true }),
|
|
52
|
+
richText('content'),
|
|
53
|
+
select('status', {
|
|
54
|
+
options: [
|
|
55
|
+
{ label: 'Draft', value: 'draft' },
|
|
56
|
+
{ label: 'Published', value: 'published' },
|
|
57
|
+
],
|
|
58
|
+
defaultValue: 'draft',
|
|
59
|
+
}),
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Adding a New Collection
|
|
65
|
+
|
|
66
|
+
1. Create `src/collections/<name>.ts` with `defineCollection()`
|
|
67
|
+
2. Add to `collections` array in `momentum.config.ts`
|
|
68
|
+
3. Run `npx drizzle-kit generate && npx drizzle-kit push`
|
|
69
|
+
4. Restart dev server
|
|
70
|
+
|
|
71
|
+
### Field Types
|
|
72
|
+
|
|
73
|
+
| Field | Import | Description |
|
|
74
|
+
| -------------- | ----------------------------------------------- | ----------------------- |
|
|
75
|
+
| `text` | `text(name, opts)` | Short text string |
|
|
76
|
+
| `textarea` | `textarea(name, opts)` | Multi-line text |
|
|
77
|
+
| `richText` | `richText(name, opts)` | Rich text editor (HTML) |
|
|
78
|
+
| `number` | `number(name, opts)` | Numeric value |
|
|
79
|
+
| `date` | `date(name, opts)` | Date/datetime |
|
|
80
|
+
| `checkbox` | `checkbox(name, opts)` | Boolean |
|
|
81
|
+
| `select` | `select(name, { options })` | Dropdown select |
|
|
82
|
+
| `radio` | `radio(name, { options })` | Radio buttons |
|
|
83
|
+
| `email` | `email(name, opts)` | Email with validation |
|
|
84
|
+
| `password` | `password(name, opts)` | Password field |
|
|
85
|
+
| `upload` | `upload(name, { relationTo })` | File upload |
|
|
86
|
+
| `relationship` | `relationship(name, { collection: () => Ref })` | FK reference |
|
|
87
|
+
| `array` | `array(name, { fields })` | Repeating sub-fields |
|
|
88
|
+
| `group` | `group(name, { fields })` | Nested field group |
|
|
89
|
+
| `blocks` | `blocks(name, { blocks })` | Content blocks |
|
|
90
|
+
| `json` | `json(name, opts)` | Raw JSON |
|
|
91
|
+
| `point` | `point(name, opts)` | Geolocation |
|
|
92
|
+
| `slug` | `slug(name, { from })` | Auto-generated slug |
|
|
93
|
+
|
|
94
|
+
### Access Control Helpers
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { allowAll, isAuthenticated, hasRole, hasAnyRole, isOwner, and, or, not } from '@momentumcms/core';
|
|
98
|
+
|
|
99
|
+
access: {
|
|
100
|
+
read: allowAll(),
|
|
101
|
+
create: isAuthenticated(),
|
|
102
|
+
update: or(hasRole('admin'), isOwner()),
|
|
103
|
+
delete: hasRole('admin'),
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Using the API in Components
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { injectMomentumAPI } from '@momentumcms/admin';
|
|
111
|
+
|
|
112
|
+
@Component({...})
|
|
113
|
+
export class MyComponent {
|
|
114
|
+
private readonly api = injectMomentumAPI();
|
|
115
|
+
|
|
116
|
+
// Observable
|
|
117
|
+
readonly posts$ = this.api.collection<Post>('posts').find$({ limit: 10 });
|
|
118
|
+
|
|
119
|
+
// Promise
|
|
120
|
+
async load(): Promise<void> {
|
|
121
|
+
const result = await this.api.collection<Post>('posts').find({ limit: 10 });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// CRUD
|
|
125
|
+
async create(): Promise<void> {
|
|
126
|
+
await this.api.collection<Post>('posts').create({ title: 'New' });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Code Style
|
|
132
|
+
|
|
133
|
+
- **Signals for state**: `signal()`, `computed()`, `effect()`
|
|
134
|
+
- **Signal inputs/outputs**: `input()`, `input.required()`, `output()`
|
|
135
|
+
- **inject() function**, not constructor injection
|
|
136
|
+
- **OnPush change detection** for all components
|
|
137
|
+
- **Control flow**: `@if`, `@for`, `@switch`
|
|
138
|
+
- Don't add `standalone: true` (default in Angular 21)
|
|
139
|
+
- Use kebab-case filenames, PascalCase classes
|
|
140
|
+
|
|
141
|
+
## REST API
|
|
142
|
+
|
|
143
|
+
All collections get auto-generated endpoints:
|
|
144
|
+
|
|
145
|
+
| Method | Path | Description |
|
|
146
|
+
| ------ | ---------------------- | -------------------------------------- |
|
|
147
|
+
| GET | `/api/:collection` | List (query: limit, page, sort, where) |
|
|
148
|
+
| GET | `/api/:collection/:id` | Get by ID |
|
|
149
|
+
| POST | `/api/:collection` | Create |
|
|
150
|
+
| PATCH | `/api/:collection/:id` | Update |
|
|
151
|
+
| DELETE | `/api/:collection/:id` | Delete |
|
|
152
|
+
|
|
153
|
+
## Documentation
|
|
154
|
+
|
|
155
|
+
Full docs: https://github.com/momentum-cms/momentum-cms/tree/main/docs
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: collection
|
|
3
|
+
description: Generate a new Momentum CMS collection with fields, access control, and hooks
|
|
4
|
+
argument-hint: <collection-name>
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Generate Momentum CMS Collection
|
|
8
|
+
|
|
9
|
+
Create a new collection file following project conventions.
|
|
10
|
+
|
|
11
|
+
## Arguments
|
|
12
|
+
|
|
13
|
+
- `$ARGUMENTS` - Collection name (e.g., "posts", "products", "pages")
|
|
14
|
+
|
|
15
|
+
## Steps
|
|
16
|
+
|
|
17
|
+
1. Create the collection file at `src/collections/<name>.ts`
|
|
18
|
+
|
|
19
|
+
2. Use this template:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import {
|
|
23
|
+
defineCollection,
|
|
24
|
+
text,
|
|
25
|
+
richText,
|
|
26
|
+
number,
|
|
27
|
+
date,
|
|
28
|
+
checkbox,
|
|
29
|
+
select,
|
|
30
|
+
relationship,
|
|
31
|
+
} from '@momentumcms/core';
|
|
32
|
+
|
|
33
|
+
export const <PascalName> = defineCollection({
|
|
34
|
+
slug: '<kebab-name>',
|
|
35
|
+
|
|
36
|
+
admin: {
|
|
37
|
+
useAsTitle: 'title', // or 'name' - the field to display as title
|
|
38
|
+
defaultColumns: ['title', 'createdAt'],
|
|
39
|
+
group: 'Content', // Admin sidebar group
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
access: {
|
|
43
|
+
read: () => true,
|
|
44
|
+
create: ({ req }) => !!req.user,
|
|
45
|
+
update: ({ req }) => req.user?.role === 'admin',
|
|
46
|
+
delete: ({ req }) => req.user?.role === 'admin',
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
hooks: {
|
|
50
|
+
beforeChange: [],
|
|
51
|
+
afterChange: [],
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
fields: [
|
|
55
|
+
text('title', { required: true }),
|
|
56
|
+
// Add more fields as needed
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
3. Add to momentum config. In `src/momentum.config.ts`, import and add to `collections` array:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { <PascalName> } from './collections/<name>';
|
|
65
|
+
|
|
66
|
+
// In config:
|
|
67
|
+
collections: [Posts, <PascalName>],
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
4. Remind user to run schema generation:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npx drizzle-kit generate
|
|
74
|
+
npx drizzle-kit push
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Field Types Available
|
|
78
|
+
|
|
79
|
+
- `text(name, options)` - Short text (minLength, maxLength)
|
|
80
|
+
- `textarea(name, options)` - Multi-line text (rows, minLength, maxLength)
|
|
81
|
+
- `richText(name, options)` - Rich text editor
|
|
82
|
+
- `number(name, options)` - Numeric value (min, max, step)
|
|
83
|
+
- `date(name, options)` - Date/datetime
|
|
84
|
+
- `checkbox(name, options)` - Boolean
|
|
85
|
+
- `select(name, { options: [...] })` - Dropdown (hasMany for multi-select)
|
|
86
|
+
- `radio(name, { options: [...] })` - Radio buttons
|
|
87
|
+
- `email(name, options)` - Email with validation
|
|
88
|
+
- `upload(name, { relationTo: 'media' })` - File upload
|
|
89
|
+
- `relationship(name, { collection: () => Ref })` - Reference to another collection
|
|
90
|
+
- `array(name, { fields: [...] })` - Array of objects (minRows, maxRows)
|
|
91
|
+
- `group(name, { fields: [...] })` - Nested object
|
|
92
|
+
- `blocks(name, { blocks: [...] })` - Content blocks
|
|
93
|
+
- `json(name, options)` - Raw JSON
|
|
94
|
+
- `point(name, options)` - Geolocation (lat/lng)
|
|
95
|
+
- `slug(name, { from: 'title' })` - Auto-generated slug
|
|
96
|
+
|
|
97
|
+
## Layout Fields (visual only, no data storage)
|
|
98
|
+
|
|
99
|
+
- `tabs(name, { tabs: [{ label, fields }] })` - Tabbed sections
|
|
100
|
+
- `collapsible(name, { fields, defaultOpen })` - Expandable section
|
|
101
|
+
- `row(name, { fields })` - Horizontal row
|
|
102
|
+
|
|
103
|
+
## Access Control Helpers
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import {
|
|
107
|
+
allowAll,
|
|
108
|
+
denyAll,
|
|
109
|
+
isAuthenticated,
|
|
110
|
+
hasRole,
|
|
111
|
+
hasAnyRole,
|
|
112
|
+
isOwner,
|
|
113
|
+
and,
|
|
114
|
+
or,
|
|
115
|
+
not,
|
|
116
|
+
} from '@momentumcms/core';
|
|
117
|
+
```
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: momentum-api
|
|
3
|
+
description: Work with Momentum API for data operations in Angular components
|
|
4
|
+
argument-hint: <operation> [collection]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Momentum API Usage
|
|
8
|
+
|
|
9
|
+
Guide for using `injectMomentumAPI()` in Angular components.
|
|
10
|
+
|
|
11
|
+
## Arguments
|
|
12
|
+
|
|
13
|
+
- `$ARGUMENTS` - Operation type: "query", "crud", "typed", or collection name
|
|
14
|
+
|
|
15
|
+
## Quick Reference
|
|
16
|
+
|
|
17
|
+
### Inject the API
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { injectMomentumAPI } from '@momentumcms/admin';
|
|
21
|
+
|
|
22
|
+
@Component({...})
|
|
23
|
+
export class MyComponent {
|
|
24
|
+
private readonly api = injectMomentumAPI();
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Query Data (Observables)
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// In constructor or with toSignal()
|
|
32
|
+
this.api
|
|
33
|
+
.collection<Post>('posts')
|
|
34
|
+
.find$({ limit: 10 })
|
|
35
|
+
.subscribe((result) => {
|
|
36
|
+
this.posts.set(result.docs);
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Query Data (Promises)
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
async loadData(): Promise<void> {
|
|
44
|
+
const result = await this.api.collection<Post>('posts').find({ limit: 10 });
|
|
45
|
+
this.posts.set(result.docs);
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### CRUD Operations
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// Create
|
|
53
|
+
const post = await this.api.collection<Post>('posts').create({ title: 'New Post' });
|
|
54
|
+
|
|
55
|
+
// Read
|
|
56
|
+
const post = await this.api.collection<Post>('posts').findById('123');
|
|
57
|
+
|
|
58
|
+
// Update
|
|
59
|
+
const updated = await this.api.collection<Post>('posts').update('123', { title: 'Updated' });
|
|
60
|
+
|
|
61
|
+
// Delete
|
|
62
|
+
const result = await this.api.collection<Post>('posts').delete('123');
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Find Options
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
interface FindOptions {
|
|
69
|
+
where?: Record<string, unknown>; // Filter conditions
|
|
70
|
+
sort?: string; // Sort field (prefix with - for desc)
|
|
71
|
+
limit?: number; // Max results (default: 10)
|
|
72
|
+
page?: number; // Page number (default: 1)
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Full Component Example
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
|
|
80
|
+
import { injectMomentumAPI } from '@momentumcms/admin';
|
|
81
|
+
|
|
82
|
+
@Component({
|
|
83
|
+
selector: 'app-posts',
|
|
84
|
+
template: `
|
|
85
|
+
@if (loading()) {
|
|
86
|
+
<p>Loading...</p>
|
|
87
|
+
} @else {
|
|
88
|
+
@for (post of posts(); track post.id) {
|
|
89
|
+
<article>
|
|
90
|
+
<h2>{{ post.title }}</h2>
|
|
91
|
+
<button (click)="deletePost(post.id)">Delete</button>
|
|
92
|
+
</article>
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
`,
|
|
96
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
97
|
+
})
|
|
98
|
+
export class PostsComponent {
|
|
99
|
+
private readonly api = injectMomentumAPI();
|
|
100
|
+
|
|
101
|
+
readonly posts = signal<any[]>([]);
|
|
102
|
+
readonly loading = signal(true);
|
|
103
|
+
|
|
104
|
+
constructor() {
|
|
105
|
+
this.loadPosts();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async loadPosts(): Promise<void> {
|
|
109
|
+
this.loading.set(true);
|
|
110
|
+
try {
|
|
111
|
+
const result = await this.api.collection('posts').find({
|
|
112
|
+
limit: 20,
|
|
113
|
+
sort: '-createdAt',
|
|
114
|
+
});
|
|
115
|
+
this.posts.set(result.docs);
|
|
116
|
+
} finally {
|
|
117
|
+
this.loading.set(false);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async deletePost(id: string): Promise<void> {
|
|
122
|
+
await this.api.collection('posts').delete(id);
|
|
123
|
+
this.posts.update((posts) => posts.filter((p) => p.id !== id));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Platform Behavior
|
|
129
|
+
|
|
130
|
+
- **SSR**: Direct database access (no HTTP overhead)
|
|
131
|
+
- **Browser**: HTTP calls to `/api/*`
|
|
132
|
+
- **Same interface** - code works identically on both platforms
|
|
133
|
+
|
|
134
|
+
## TransferState (SSR Hydration)
|
|
135
|
+
|
|
136
|
+
TransferState is **enabled by default** for read operations. Data fetched during SSR is cached and reused on browser hydration.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// Default: TransferState enabled (no duplicate fetch on hydration)
|
|
140
|
+
const posts = await this.api.collection<Post>('posts').find({ limit: 10 });
|
|
141
|
+
|
|
142
|
+
// Opt-out: always fetch fresh data
|
|
143
|
+
const posts = await this.api.collection<Post>('posts').find({ limit: 10, transfer: false });
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Requires `provideClientHydration()` in app config.
|
|
147
|
+
|
|
148
|
+
## Error Handling
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
try {
|
|
152
|
+
await this.api.collection('posts').create({ title: '' });
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error instanceof ValidationError) {
|
|
155
|
+
console.error('Validation failed:', error.errors);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
A [Momentum CMS](https://github.com/momentum-cms/momentum-cms) application.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Copy environment variables
|
|
9
|
+
cp .env.example .env
|
|
10
|
+
|
|
11
|
+
# Start the dev server
|
|
12
|
+
npm run dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Open [http://localhost:{{defaultPort}}/admin](http://localhost:{{defaultPort}}/admin) to access the admin dashboard.
|
|
16
|
+
|
|
17
|
+
## Scripts
|
|
18
|
+
|
|
19
|
+
| Command | Description |
|
|
20
|
+
|---------|-------------|
|
|
21
|
+
| `npm run dev` | Start development server |
|
|
22
|
+
| `npm run build` | Production build |
|
|
23
|
+
| `npm start` | Start production server |
|
|
24
|
+
|
|
25
|
+
## Learn More
|
|
26
|
+
|
|
27
|
+
- [Momentum CMS Documentation](https://github.com/momentum-cms/momentum-cms)
|